Skip to main content

Proxy fal.ai FLUX Image Generation via CloudBase Cloud Function

In one sentence: Use @fal-ai/client inside a CloudBase Cloud Function to call the FLUX schnell model, pull the generated image binary back via fetch, write it to Cloud Storage with @cloudbase/node-sdk, and return a temporary URL via getTempFileURL for the frontend to display.

Estimated time: 25 minutes | Difficulty: Beginner

Applicable Scenarios

  • A Mini Program or web app that wants AI-generated images (avatars, product shots, marketing assets, placeholder illustrations) without running a model locally.
  • The fal.ai key cannot be exposed to the frontend, but you don't want to stand up a separate server between the frontend and fal.
  • You want the generated images stored in your own Cloud Storage rather than relying on a fal CDN link (which can expire or be purged).
  • In the same Cloud Function environment, you plan to post-process the image (crop, watermark, write to a database).

Not applicable:

  • Scenarios requiring very low latency where users are sensitive to generation time (this recipe uses synchronous subscribe; one image typically takes 3–15 seconds while the Cloud Function waits).
  • High-volume batch generation (tens of images at a time); consider fal's queue mode + a database state machine instead — not covered here.
  • Running a model directly in the browser (WebGPU) — that is a different approach and does not require a Cloud Function.

Prerequisites

DependencyVersion
Node.js (Cloud Function runtime)≥ 18 (built-in fetch; older versions need node-fetch)
@fal-ai/client^1.10.1 (note: @fal-ai/serverless-client is deprecated — do not use it)
@cloudbase/node-sdk^3.18.1
@cloudbase/clilatest
Cloud Function typeHTTP trigger (--httpFn)
Public network egressCalling fal.ai requires public internet access; confirm "Network → Public Access" is enabled in the Console

You will need:

  • A fal.ai account with an API key from fal.ai dashboard.
  • A CloudBase environment ID with Cloud Functions and Cloud Storage enabled.

Step 1: Get a fal.ai Key and Add It as a Cloud Function Environment Variable

  1. Open https://fal.ai/dashboard/keys, click Add Key, and copy the generated string (it is shown only once).
  2. This key will be injected into the Cloud Function as the environment variable FAL_KEY. Do not put it in your code or commit it to git.
  3. You can add it in the CloudBase Console under "Cloud Functions → Environment Variables" now or after deployment — either order works.

The fal.ai SDK automatically reads credentials from process.env.FAL_KEY, so the variable name must be exactly FAL_KEY.

Step 2: Write the Cloud Function

Create a directory fal-image-gen and initialize it:

mkdir fal-image-gen && cd fal-image-gen
npm init -y
npm install --save @fal-ai/client @cloudbase/node-sdk

The main field in package.json defaults to index.js, which is correct.

index.js:

const { fal } = require('@fal-ai/client');
const tcb = require('@cloudbase/node-sdk');

// Read FAL_KEY from environment variable — no key in source code
fal.config({ credentials: process.env.FAL_KEY });

const app = tcb.init({
env: process.env.TCB_ENV || tcb.SYMBOL_CURRENT_ENV,
});

exports.main = async (event) => {
// event.body is the HTTP request body string — parse it as JSON
let body = {};
try {
body = typeof event.body === 'string' ? JSON.parse(event.body) : (event.body || {});
} catch (err) {
return httpJson(400, { error: 'invalid_json', message: err.message });
}

const prompt = body.prompt;
const imageSize = body.image_size || 'landscape_16_9';
const model = body.model || 'fal-ai/flux/schnell';

if (!prompt || typeof prompt !== 'string') {
return httpJson(400, { error: 'missing_prompt' });
}

// 1. Call fal.ai to generate the image (synchronous — waits for the result)
let falResult;
try {
falResult = await fal.subscribe(model, {
input: { prompt, image_size: imageSize },
});
} catch (err) {
console.error('fal.subscribe failed', err);
return httpJson(502, { error: 'fal_failed', message: err.message });
}

const images = falResult?.data?.images || [];
if (images.length === 0) {
return httpJson(502, { error: 'no_image_returned' });
}
const imageUrl = images[0].url;

// 2. Pull the image binary back via fetch
let buffer;
try {
const resp = await fetch(imageUrl);
if (!resp.ok) {
return httpJson(502, { error: 'fetch_image_failed', status: resp.status });
}
buffer = Buffer.from(await resp.arrayBuffer());
} catch (err) {
console.error('fetch image failed', err);
return httpJson(502, { error: 'fetch_image_failed', message: err.message });
}

// 3. Write to Cloud Storage — use date + random suffix to avoid collisions
const ext = guessExt(imageUrl) || 'png';
const cloudPath = `ai-images/${dateStr()}/${Date.now()}-${rand6()}.${ext}`;
let uploadResult;
try {
uploadResult = await app.uploadFile({
cloudPath,
fileContent: buffer,
});
} catch (err) {
console.error('uploadFile failed', err);
return httpJson(500, { error: 'upload_failed', message: err.message });
}

// 4. Get a temporary URL (1 hour) for the frontend to use directly
const tmp = await app.getTempFileURL({
fileList: [{ fileID: uploadResult.fileID, maxAge: 3600 }],
});
const tempFileURL = tmp.fileList?.[0]?.tempFileURL;

return httpJson(200, {
fileID: uploadResult.fileID,
tempFileURL,
model,
prompt,
});
};

function httpJson(statusCode, data) {
return {
statusCode,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
};
}

function dateStr() {
const d = new Date();
const m = String(d.getUTCMonth() + 1).padStart(2, '0');
const day = String(d.getUTCDate()).padStart(2, '0');
return `${d.getUTCFullYear()}-${m}-${day}`;
}

function rand6() {
return Math.random().toString(36).slice(2, 8);
}

function guessExt(url) {
const m = url.match(/\.([a-zA-Z0-9]{2,5})(\?|$)/);
return m ? m[1].toLowerCase() : null;
}

A few implementation notes:

  • fal.subscribe(...) waits synchronously for the result — suitable for schnell, which typically returns in a few seconds. For slower models like fal-ai/flux-pro or fal-ai/flux-kontext/dev, increase the Cloud Function timeout accordingly (see below).
  • falResult.data.images is an array; by default only one image is generated, but the model supports a num_images parameter.
  • Do not concatenate user input directly into cloudPath, and never include ..uploadFile will reject it.
  • fetch is built into Node.js 18. Set the Cloud Function runtime to 18 or later and node-fetch is not needed.

Step 3: Deploy the Cloud Function

Log in to the CLI (interactive login locally, or use an API key in CI):

# Local
tcb login

# CI environment
tcb login --apiKeyId <YOUR_API_KEY_ID> --apiKey <YOUR_API_KEY_SECRET>

Deploy as an HTTP function:

tcb fn deploy fal-image-gen --httpFn -e your-env-id

After a successful deployment, go to the Console under "Cloud Functions → fal-image-gen" and do two things:

  1. Environment Variables: add FAL_KEY with the value from Step 1.
  2. Timeout: fal schnell usually takes 3–8 seconds but can occasionally exceed 15 seconds. The default 3-second timeout will cause failures — set it to at least 60 seconds.

Note the access URL, which looks like https://your-env.service.tcloudbase.com/fal-image-gen.

Step 4: Frontend Integration and Display

Use a plain fetch call to invoke the Cloud Function and assign the returned tempFileURL to an <img> element:

<button id="gen">Generate Image</button>
<img id="preview" style="max-width: 600px" />

<script>
document.getElementById('gen').onclick = async () => {
const resp = await fetch('https://your-env.service.tcloudbase.com/fal-image-gen', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
prompt: 'a calico cat sitting on a window sill at sunset, soft light',
image_size: 'landscape_16_9',
}),
});
const data = await resp.json();
if (data.tempFileURL) {
document.getElementById('preview').src = data.tempFileURL;
} else {
alert('Generation failed: ' + JSON.stringify(data));
}
};
</script>

The temporary URL's validity is controlled by maxAge (set to 3600 seconds here). For persistent display, store the fileID in your database and call getTempFileURL at display time. Do not store the tempFileURL itself in the database — it will return 404 once it expires.

Verification

The most direct way to verify is with curl:

curl -X POST 'https://your-env.service.tcloudbase.com/fal-image-gen' \
-H 'Content-Type: application/json' \
-d '{"prompt": "a calico cat at sunset", "image_size": "landscape_16_9"}'

Expected response:

{
"fileID": "cloud://your-env.xxxx-xxx/ai-images/2026-04-30/1714451234567-abc123.png",
"tempFileURL": "https://...service.tcloudbase.com/...?sign=...",
"model": "fal-ai/flux/schnell",
"prompt": "a calico cat at sunset"
}

Paste the tempFileURL into a browser — you should see the generated image directly.

In the Console, go to "Cloud Functions → fal-image-gen → Logs" and filter by requestId to inspect fal.ai call latency and confirm the upload succeeded.

Common Errors

ErrorCauseFix
401 Unauthorized or fal SDK throws Authentication requiredFAL_KEY is missing, wrong, or revokedRe-enter the environment variable in the Console, then redeploy the function for the new value to take effect; or regenerate the key in the fal dashboard
fal SDK throws Model not found or 404Model ID is misspelled, e.g. fal-ai/flux-schnell instead of fal-ai/flux/schnellModel IDs use forward slashes as separators. Correct IDs: fal-ai/flux/schnell, fal-ai/flux/dev, fal-ai/flux-pro, fal-ai/flux-kontext/dev, fal-ai/flux-2-flex/edit
uploadFile returns INVALID_PARAM or cloudPath invalidcloudPath contains .., a leading /, or special charactersUse relative paths with only alphanumerics, -, _, /, and .; never concatenate user input directly into cloudPath
Cloud Function disconnects after 3 seconds; fal has not returned yet in the logsDefault timeout is 3 seconds; image generation typically takes 5–15 secondsConsole → Function Config → Timeout, set to 60 s (schnell) or 120–300 s (flux-pro / kontext)
JavaScript heap out of memory or function OOMDefault memory is 256 MB; large images (2K+) can exceed this when combined with SDK overheadConsole → Function Config → Memory, increase to 512 MB or 1024 MB
getaddrinfo EAI_AGAIN fal.runCloud Function has no public network egressEnable Public Access in the Console under "Network", or attach an egress NAT

For the complete error code reference, see https://docs.cloudbase.net/error-code/.

Next Steps

Wrap this Cloud Function as an MCP tool and feed it to the server in Deploy MCP Server to Cloud Run, so an agent can generate an image simply by saying "draw a cat." Alternatively, register it as a tool for a Mastra agent and let the agent decide when an image is needed.