Skip to content
Adapters

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

shell
npm i @tanstack/ai-fal

2. Set the API key

Create a key at fal.ai. Put it in the environment of your server:

shell
FAL_KEY=your-fal-api-key

All fal adapters read FAL_KEY. To pass the key in code, set apiKey in the config:

ts
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:

ts
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

tsx
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:

ts
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:

ts
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:

ts
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:

ts
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:

ts
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 audio

See Text-to-Speech.

Transcription

ts
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:

ts
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:

ts
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

ModelDescription
fal-ai/nano-banana-proFast, high-quality images, up to 4K
fal-ai/flux-2/klein/9bRealistic images, clear text
fal-ai/z-image/turboA very fast 6B-parameter model
xai/grok-imagine-imagexAI images with prompt enhancement

Video models

ModelModeDescription
fal-ai/kling-video/v2.6/pro/text-to-videoText-to-videoHigh-quality text-to-video
fal-ai/kling-video/v2.6/pro/image-to-videoImage-to-videoAnimate images with Kling
fal-ai/veo3.1Text-to-videoGoogle Veo text-to-video
fal-ai/veo3.1/image-to-videoImage-to-videoGoogle Veo image-to-video
xai/grok-imagine-video/text-to-videoText-to-videoxAI video from text
xai/grok-imagine-video/image-to-videoImage-to-videoxAI video from an image
fal-ai/ltx-2/text-to-video/fastText-to-videoFast text-to-video
fal-ai/ltx-2/image-to-video/fastImage-to-videoFast image-to-video

Live models

ModelDescription
minimax/h3-max/directorA live stream that you can steer. Use falLiveVideo() with generateLiveVideo().

Text-to-speech models

ModelDescription
fal-ai/gemini-3.1-flash-ttsGoogle TTS with 80+ languages and style tags
fal-ai/elevenlabs/tts/eleven-v3ElevenLabs v3, expressive, several voices
fal-ai/elevenlabs/tts/turbo-v2.5ElevenLabs, low latency
fal-ai/minimax/speech-2.6-hdMiniMax HD speech
fal-ai/minimax/speech-2.6-turboMiniMax, low latency
fal-ai/kokoro/american-englishKokoro. Also british-english, french, spanish, italian, japanese, mandarin-chinese, hindi, brazilian-portuguese
fal-ai/inworld-ttsInworld TTS-1.5 Max
fal-ai/chatterbox/text-to-speech/multilingualChatterbox, many languages
fal-ai/dia-ttsDia, expressive dialogue
fal-ai/orpheus-ttsOrpheus, open source
fal-ai/f5-ttsF5-TTS, voice cloning
fal-ai/vibevoice/7bVibeVoice 7B, conversation

Transcription models

ModelDescription
fal-ai/whisperOpenAI Whisper on fal
fal-ai/wizperA faster Whisper with word timestamps
fal-ai/speech-to-text/turboFast speech-to-text with speaker labels
fal-ai/elevenlabs/speech-to-textElevenLabs speech-to-text

Music and audio models

ModelModeDescription
fal-ai/minimax-music/v2.6MusicMiniMax Music 2.6, vocals and instruments from a prompt
fal-ai/minimax-music/v2.5MusicMiniMax Music 2.5
fal-ai/minimax-music/v2MusicMiniMax Music v2, takes lyrics_prompt
fal-ai/diffrhythmMusicDiffRhythm, prompt and lyrics
fal-ai/lyria2MusicGoogle Lyria 2
fal-ai/stable-audio-25/text-to-audioMusic or audioStability AI Stable Audio 2.5
fal-ai/mmaudio-v2/text-to-audioAudioMMAudio v2
fal-ai/elevenlabs/sound-effects/v2Sound effectsElevenLabs sound effects
fal-ai/beatoven/sound-effect-generationSound effectsBeatoven sound effects
fal-ai/thinksoundAudioThinksound 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:

FormsizeSent 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:

Modelduration typeavailableDurations()
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-01not accepted{ kind: "none" }
fal-ai/hunyuan-video-v1.5/text-to-videonot accepted (uses num_frames){ kind: "none" }
ts
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.
ts
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.

FactoryReturnsUse it with
falImage(model, config?)FalImageAdaptergenerateImage()
falVideo(model, config?)FalVideoAdaptergenerateVideo(), getVideoJobStatus()
falLiveVideo(model, config?) / createFalLiveVideo(model, apiKey, config?)FalLiveVideoAdaptergenerateLiveVideo()
falSpeech(model, config?)FalSpeechAdaptergenerateSpeech(). The audio comes back as base64 in result.audio.
falTranscription(model, config?)FalTranscriptionAdaptergenerateTranscription()
falAudio(model, config?)FalAudioAdaptergenerateAudio(). The result has a URL at result.audio.url.
falFiles(config?)FalFilesAdapterThe 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.