- OpenAIUpdated
- OpenAI: AdvancedNew
- AnthropicUpdated
- Anthropic: AdvancedNew
- Google GeminiUpdated
- Google Gemini: AdvancedNew
- Google Vertex AIUpdated
- OllamaUpdated
- Grok (xAI)Updated
- GroqUpdated
- MistralUpdated
- CohereUpdated
- TypeSafeUpdated
- OllayaUpdated
- ElevenLabsUpdated
- fal.aiUpdated
- ReactorUpdated
- World LabsUpdated
- OpenRouter AdapterUpdated
- OpenRouter: AdvancedNew
- Perplexity SearchUpdated
- Vercel AI GatewayUpdated
- Lovable AI GatewayUpdated
- OpenAI-CompatibleUpdated
- OpenAI-Compatible: AdvancedNew
- LLM GatewayUpdated
- CloudflareUpdated
- Neon AI GatewayNew
- Claude CodeUpdated
- CodexUpdated
- OpenCodeUpdated
- Grok BuildUpdated
- ACP-CompatibleUpdated
- Amazon BedrockUpdated
- BytePlusUpdated
- BytePlus: AdvancedNew
fal.ai
The fal.ai adapter calls hosted models for images, video, live video, speech, transcription, music, and sound effects. It has no text chat and no tools.
The quick setup below generates an image on your server and shows it in React. For a full app, see the media example.
1. Install#
npm i @tanstack/ai-falpnpm add @tanstack/ai-falyarn add @tanstack/ai-falbun add @tanstack/ai-fal2. Set the API key#
Create a key at fal.ai. Put it in the environment of your server:
FAL_KEY=your-fal-api-keyAll fal adapters read FAL_KEY. To pass the key in code, set apiKey in the config:
import { falImage } from "@tanstack/ai-fal";
const adapter = falImage("fal-ai/nano-banana-pro", {
apiKey: process.env.FAL_KEY!,
});3. Generate an image on the server#
Add a POST route at /api/generate/image:
import {
generateImage,
generationParamsFromRequest,
toServerSentEventsResponse,
} from "@tanstack/ai";
import { falImage } from "@tanstack/ai-fal";
export async function POST(request: Request) {
const { input, threadId } = await generationParamsFromRequest("image", request);
if (typeof input.prompt !== "string") {
return new Response("Send a text prompt.", { status: 400 });
}
const stream = generateImage({
adapter: falImage("fal-ai/nano-banana-pro"),
prompt: input.prompt,
threadId,
stream: true,
});
return toServerSentEventsResponse(stream);
}4. Show the image#
import { fetchServerSentEvents, useGenerateImage } from "@tanstack/ai-react";
export function ImageGenerator() {
const { generate, result, isLoading, error } = useGenerateImage({
connection: fetchServerSentEvents("/api/generate/image"),
threadId: "image-studio",
});
return (
<div>
<button
onClick={() => generate({ prompt: "A futuristic cityscape at sunset" })}
disabled={isLoading}
>
{isLoading ? "Generating..." : "Generate"}
</button>
{error && <p role="alert">{error.message}</p>}
{result?.images.map((image, index) => (
<img
key={index}
src={image.url || `data:image/png;base64,${image.b64Json}`}
alt="Generated image"
/>
))}
</div>
);
}Click the button. The image shows up below it. For the other frameworks, see Generation Hooks.
Model options#
Each fal model has its own options. Write the model id as a string literal, and TypeScript completes size and modelOptions for that model:
import { generateImage } from "@tanstack/ai";
import { falImage } from "@tanstack/ai-fal";
const result = await generateImage({
adapter: falImage("fal-ai/z-image/turbo"),
prompt: "A serene mountain landscape",
threadId: "thread-1",
numberOfImages: 1,
size: "landscape_16_9",
modelOptions: {
acceleration: "high",
enable_prompt_expansion: true,
},
});
console.log(result.images);- Pass the id directly, not through a string variable. A variable loses the types of the model.
- Any string works as a model id. A model that is newer than the fal SDK types runs, but has no autocomplete.
For the size formats, see Image sizes.
Video#
Video generation is an experimental feature.
Video runs as a job. generateVideo() starts the job, and getVideoJobStatus() reads its status:
import { generateVideo, getVideoJobStatus } from "@tanstack/ai";
import { falVideo } from "@tanstack/ai-fal";
const adapter = falVideo("fal-ai/kling-video/v2.6/pro/text-to-video");
const job = await generateVideo({
adapter,
prompt: "A timelapse of a flower blooming",
threadId: "thread-1",
size: "16:9",
duration: "5",
});
const status = await getVideoJobStatus({ adapter, jobId: job.jobId });
console.log(status.status); // "pending" | "processing" | "completed"To animate an image, use an image-to-video model and set the start image in modelOptions:
import { generateVideo } from "@tanstack/ai";
import { falVideo } from "@tanstack/ai-fal";
const job = await generateVideo({
adapter: falVideo("fal-ai/kling-video/v2.6/pro/image-to-video"),
prompt: "Animate this scene with gentle wind",
threadId: "thread-1",
duration: "5",
modelOptions: {
start_image_url: "https://example.com/image.jpg",
generate_audio: true,
},
});
console.log(job.jobId);The duration values of each model are in Video durations. To poll from a React component, see Video Generation.
Live video#
falLiveVideo("minimax/h3-max/director") starts a live video stream that you can steer. The browser connects through a proxy on your server. See Live Generation.
Speech#
falSpeech gets the audio from the fal CDN and returns it as base64:
import { generateSpeech } from "@tanstack/ai";
import { falSpeech } from "@tanstack/ai-fal";
const result = await generateSpeech({
adapter: falSpeech("fal-ai/gemini-3.1-flash-tts"),
text: "[warm, enthusiastic] Welcome to TanStack AI! [pause] Let's build something great.",
threadId: "thread-1",
voice: "Kore",
});
console.log(result.format, result.contentType);fal-ai/gemini-3.1-flash-tts is the TTS model of Google. It supports 80+ languages. You can put speaker tags and style tags, such as [pause], in the text. Its modelOptions have no autocomplete yet.
Other models put the voice in modelOptions:
import { generateSpeech } from "@tanstack/ai";
import { falSpeech } from "@tanstack/ai-fal";
const result = await generateSpeech({
adapter: falSpeech("fal-ai/elevenlabs/tts/eleven-v3"),
text: "Welcome to TanStack AI.",
threadId: "thread-1",
modelOptions: {
voice: "Rachel",
stability: 0.5,
},
});
console.log(result.audio); // Base64-encoded audioSee Text-to-Speech.
Transcription#
import { generateTranscription } from "@tanstack/ai";
import { falTranscription } from "@tanstack/ai-fal";
const result = await generateTranscription({
adapter: falTranscription("fal-ai/whisper"),
audio: "https://example.com/recording.mp3",
language: "en",
threadId: "thread-1",
});
console.log(result.text);
console.log(result.language);
// When the model returns timestamps, they are in result.segments
for (const segment of result.segments ?? []) {
console.log(`[${segment.start}s to ${segment.end}s] ${segment.text}`);
}audio takes a URL string, a Blob, a File, or an ArrayBuffer. The adapter wraps an ArrayBuffer in a Blob for the upload.
Music and sound effects#
falAudio returns a URL in result.audio.url. If you need the bytes, fetch the URL:
import { generateAudio } from "@tanstack/ai";
import { falAudio } from "@tanstack/ai-fal";
const music = await generateAudio({
adapter: falAudio("fal-ai/minimax-music/v2.6"),
prompt: "City Pop, 80s retro, groovy synth bass, warm female vocal, 104 BPM, nostalgic urban night",
threadId: "thread-1",
});
const sfx = await generateAudio({
adapter: falAudio("fal-ai/elevenlabs/sound-effects/v2"),
prompt: "Thunderclap with rain",
threadId: "thread-1",
duration: 5,
});
console.log(music.audio.url, sfx.audio.url);Some music models take lyrics in modelOptions:
import { generateAudio } from "@tanstack/ai";
import { falAudio } from "@tanstack/ai-fal";
const song = await generateAudio({
adapter: falAudio("fal-ai/diffrhythm"),
prompt: "An upbeat electronic track with synths",
threadId: "thread-1",
modelOptions: {
lyrics: "[verse]\nHello world\n[chorus]\nLa la la",
},
});
console.log(song.audio.url);See Audio Generation.
Files#
falFiles uploads media to fal storage, so a request can point to the file by URL. fal storage is upload-only. See Files API.
Models and options#
fal has hundreds of models. These are popular picks. Any fal model id works.
Image models#
| Model | Description |
|---|---|
| fal-ai/nano-banana-pro | Fast, high-quality images, up to 4K |
| fal-ai/flux-2/klein/9b | Realistic images, clear text |
| fal-ai/z-image/turbo | A very fast 6B-parameter model |
| xai/grok-imagine-image | xAI images with prompt enhancement |
Video models#
| Model | Mode | Description |
|---|---|---|
| fal-ai/kling-video/v2.6/pro/text-to-video | Text-to-video | High-quality text-to-video |
| fal-ai/kling-video/v2.6/pro/image-to-video | Image-to-video | Animate images with Kling |
| fal-ai/veo3.1 | Text-to-video | Google Veo text-to-video |
| fal-ai/veo3.1/image-to-video | Image-to-video | Google Veo image-to-video |
| xai/grok-imagine-video/text-to-video | Text-to-video | xAI video from text |
| xai/grok-imagine-video/image-to-video | Image-to-video | xAI video from an image |
| fal-ai/ltx-2/text-to-video/fast | Text-to-video | Fast text-to-video |
| fal-ai/ltx-2/image-to-video/fast | Image-to-video | Fast image-to-video |
Live models#
| Model | Description |
|---|---|
| minimax/h3-max/director | A live stream that you can steer. Use falLiveVideo() with generateLiveVideo(). |
Text-to-speech models#
| Model | Description |
|---|---|
| fal-ai/gemini-3.1-flash-tts | Google TTS with 80+ languages and style tags |
| fal-ai/elevenlabs/tts/eleven-v3 | ElevenLabs v3, expressive, several voices |
| fal-ai/elevenlabs/tts/turbo-v2.5 | ElevenLabs, low latency |
| fal-ai/minimax/speech-2.6-hd | MiniMax HD speech |
| fal-ai/minimax/speech-2.6-turbo | MiniMax, low latency |
| fal-ai/kokoro/american-english | Kokoro. Also british-english, french, spanish, italian, japanese, mandarin-chinese, hindi, brazilian-portuguese |
| fal-ai/inworld-tts | Inworld TTS-1.5 Max |
| fal-ai/chatterbox/text-to-speech/multilingual | Chatterbox, many languages |
| fal-ai/dia-tts | Dia, expressive dialogue |
| fal-ai/orpheus-tts | Orpheus, open source |
| fal-ai/f5-tts | F5-TTS, voice cloning |
| fal-ai/vibevoice/7b | VibeVoice 7B, conversation |
Transcription models#
| Model | Description |
|---|---|
| fal-ai/whisper | OpenAI Whisper on fal |
| fal-ai/wizper | A faster Whisper with word timestamps |
| fal-ai/speech-to-text/turbo | Fast speech-to-text with speaker labels |
| fal-ai/elevenlabs/speech-to-text | ElevenLabs speech-to-text |
Music and audio models#
| Model | Mode | Description |
|---|---|---|
| fal-ai/minimax-music/v2.6 | Music | MiniMax Music 2.6, vocals and instruments from a prompt |
| fal-ai/minimax-music/v2.5 | Music | MiniMax Music 2.5 |
| fal-ai/minimax-music/v2 | Music | MiniMax Music v2, takes lyrics_prompt |
| fal-ai/diffrhythm | Music | DiffRhythm, prompt and lyrics |
| fal-ai/lyria2 | Music | Google Lyria 2 |
| fal-ai/stable-audio-25/text-to-audio | Music or audio | Stability AI Stable Audio 2.5 |
| fal-ai/mmaudio-v2/text-to-audio | Audio | MMAudio v2 |
| fal-ai/elevenlabs/sound-effects/v2 | Sound effects | ElevenLabs sound effects |
| fal-ai/beatoven/sound-effect-generation | Sound effects | Beatoven sound effects |
| fal-ai/thinksound | Audio | Thinksound audio |
Some very new models, for example fal-ai/gemini-3.1-flash-tts, fal-ai/minimax-music/v2.6, and fal-ai/beatoven/sound-effect-generation, are not in the type map of @fal-ai/client yet. They work as plain string ids, with no autocomplete for modelOptions.
Advanced#
Image sizes#
size maps to image_size, or to aspect_ratio and resolution:
| Form | size | Sent to fal as |
|---|---|---|
| Named | "landscape_16_9" | image_size: "landscape_16_9" |
| Width x height | "1536x1024" | image_size: "1536x1024" |
| Ratio and resolution | "16:9_4K" | aspect_ratio: "16:9", resolution: "4K" |
| Ratio only | "16:9" | aspect_ratio: "16:9" |
Named sizes change by model.
Video durations#
duration gets its type for each model from the EndpointTypeMap of @fal-ai/client. Popular models also have availableDurations() and snapDuration(), for a UI slider:
| Model | duration type | availableDurations() |
|---|---|---|
| fal-ai/kling-video/v1.6/{standard,pro}/text-to-video, fal-ai/kling-video/v2.6/pro/{text,image}-to-video | "5" or "10" | discrete |
| fal-ai/kling-video/v3/pro/{text,image}-to-video | "3" to "15" | discrete |
| fal-ai/pika/v2.2/text-to-video | "5" or "10" | discrete |
| fal-ai/ltx-2.3/{text,image}-to-video (and /fast) | "6", "8", or "10" | discrete |
| fal-ai/luma-dream-machine/ray-2 | "5s" or "9s" | discrete |
| fal-ai/veo3.1, fal-ai/veo3.1/fast (and /image-to-video), fal-ai/veo3 (and /image-to-video) | "4s", "6s", or "8s" | discrete |
| fal-ai/wan-25-preview/text-to-video | "2" to "15" | discrete |
| fal-ai/minimax/video-01 | not accepted | { kind: "none" } |
| fal-ai/hunyuan-video-v1.5/text-to-video | not accepted (uses num_frames) | { kind: "none" } |
import { generateVideo } from "@tanstack/ai";
import { falVideo } from "@tanstack/ai-fal";
const adapter = falVideo("fal-ai/veo3.1");
adapter.availableDurations(); // { kind: "discrete", values: ["4s", "6s", "8s"] }
adapter.snapDuration(7); // "6s"
await generateVideo({
adapter,
prompt: "A timelapse of a city skyline at dusk",
threadId: "thread-1",
duration: adapter.snapDuration(7),
});Other models still get the duration type from the SDK when the endpoint has the field. For these models, availableDurations() returns { kind: "none" }.
Configuration#
Each factory takes an optional FalClientConfig:
- apiKey: your fal key. If you do not set a config, the adapter reads FAL_KEY.
- proxyUrl: a proxy URL on your server, for calls from the browser.
- fetch: a custom fetch for the fal requests. The adapter wraps it to read the billed units.
import { falImage } from "@tanstack/ai-fal";
const adapter = falImage("fal-ai/nano-banana-pro", {
apiKey: process.env.FAL_KEY!,
proxyUrl: "https://your-server.com/api/fal/proxy",
});Live video proxy#
generateLiveVideo() returns the WMA app id on result.model (fal-ai/minimax-h3-max-director). In the browser:
- Open that id with wma(live.model) through a proxy on your server. The proxy attaches FAL_KEY.
- Do not send live.token as Key credentials.
- In the proxy, call allowedFalLiveVideoProxyTarget(). It lets the proxy forward only to WMA /ice, /session, /session/heartbeat, and Director /ice.
API reference#
Each factory takes model first and an optional config (apiKey, proxyUrl, fetch). Without apiKey, it reads FAL_KEY.
| Factory | Returns | Use it with |
|---|---|---|
| falImage(model, config?) | FalImageAdapter | generateImage() |
| falVideo(model, config?) | FalVideoAdapter | generateVideo(), getVideoJobStatus() |
| falLiveVideo(model, config?) / createFalLiveVideo(model, apiKey, config?) | FalLiveVideoAdapter | generateLiveVideo() |
| falSpeech(model, config?) | FalSpeechAdapter | generateSpeech(). The audio comes back as base64 in result.audio. |
| falTranscription(model, config?) | FalTranscriptionAdapter | generateTranscription() |
| falAudio(model, config?) | FalAudioAdapter | generateAudio(). The result has a URL at result.audio.url. |
| falFiles(config?) | FalFilesAdapter | The Files API |
Helpers:
- getFalApiKeyFromEnv(): reads FAL_KEY. Throws if it is not set.
- configureFalClient(config?): configures @fal-ai/client. The adapter constructors call it for you. It sets the credentials from the API key, and the proxy when you set proxyUrl.