Proxy fal.ai FLUX Image Generation via CloudBase Cloud Function
In one sentence: Use
@fal-ai/clientinside a CloudBase Cloud Function to call the FLUX schnell model, pull the generated image binary back viafetch, write it to Cloud Storage with@cloudbase/node-sdk, and return a temporary URL viagetTempFileURLfor 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
| Dependency | Version |
|---|---|
| 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/cli | latest |
| Cloud Function type | HTTP trigger (--httpFn) |
| Public network egress | Calling 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
- Open https://fal.ai/dashboard/keys, click Add Key, and copy the generated string (it is shown only once).
- 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. - 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 exactlyFAL_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 likefal-ai/flux-proorfal-ai/flux-kontext/dev, increase the Cloud Function timeout accordingly (see below).falResult.data.imagesis an array; by default only one image is generated, but the model supports anum_imagesparameter.- Do not concatenate user input directly into
cloudPath, and never include..—uploadFilewill reject it. fetchis built into Node.js 18. Set the Cloud Function runtime to 18 or later andnode-fetchis 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:
- Environment Variables: add
FAL_KEYwith the value from Step 1. - 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
| Error | Cause | Fix |
|---|---|---|
401 Unauthorized or fal SDK throws Authentication required | FAL_KEY is missing, wrong, or revoked | Re-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 404 | Model ID is misspelled, e.g. fal-ai/flux-schnell instead of fal-ai/flux/schnell | Model 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 invalid | cloudPath contains .., a leading /, or special characters | Use 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 logs | Default timeout is 3 seconds; image generation typically takes 5–15 seconds | Console → Function Config → Timeout, set to 60 s (schnell) or 120–300 s (flux-pro / kontext) |
JavaScript heap out of memory or function OOM | Default memory is 256 MB; large images (2K+) can exceed this when combined with SDK overhead | Console → Function Config → Memory, increase to 512 MB or 1024 MB |
getaddrinfo EAI_AGAIN fal.run | Cloud Function has no public network egress | Enable 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/.
Related Documentation
- Proxy Overseas LLM APIs via Cloud Function — the same "Cloud Function as proxy" pattern for text scenarios
- Upload Files to Cloud Storage from a Mini Program — foundational usage of
uploadFile/getTempFileURL - Secret Management in Cloud Functions — how to layer
FAL_KEYacross local dev, CI, and production to keep it out of git
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.