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.
npm i @tanstack/ai-falpnpm add @tanstack/ai-falyarn add @tanstack/ai-falbun add @tanstack/ai-falCreate 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!,
});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);
}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.
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);For the size formats, see Image sizes.
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.
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.
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.
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.
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.
falFiles uploads media to fal storage, so a request can point to the file by URL. fal storage is upload-only. See Files API.
fal has hundreds of models. These are popular picks. Any fal model id works.
| 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 |
| 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 |
| Model | Description |
|---|---|
| minimax/h3-max/director | A live stream that you can steer. Use falLiveVideo() with generateLiveVideo(). |
| 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 |
| 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 |
| 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.
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.
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" }.
Each factory takes an optional FalClientConfig:
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",
});generateLiveVideo() returns the WMA app id on result.model (fal-ai/minimax-h3-max-director). In the browser:
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: