Skip to main content
Technology & EngineeringImage Generation Services329 lines

fal.ai Image Generation

"fal.ai: fast inference, Flux, realtime image gen, queue API, webhooks, JavaScript SDK, serverless GPU"

Quick Summary28 lines
fal.ai is a serverless GPU inference platform optimized for speed. It runs popular image generation models like Flux with extremely low latency through optimized infrastructure and model caching. The platform offers three execution modes: synchronous (blocking), queue-based (async with polling or webhooks), and realtime (WebSocket streaming). The JavaScript SDK provides first-class TypeScript support with type-safe model inputs and outputs. fal.ai charges per request based on model and resolution, with no idle GPU costs. Choose fal when latency matters most -- it consistently delivers the fastest Flux inference available.

## Key Points

- **Use `fal.subscribe` over raw queue calls**: The subscribe method handles polling and status checking automatically with configurable intervals.
- **Choose the right Flux variant**: Schnell for speed (4 steps), Dev for quality (28 steps), Pro for production-grade output. Match the model to your latency and quality requirements.
- **Use the proxy in browser apps**: Never expose your FAL_KEY client-side. Set up the Next.js or Express proxy to keep credentials server-side.
- **Leverage realtime for interactive UIs**: The WebSocket connection eliminates HTTP overhead for rapid iteration workflows like prompt experimentation.
- **Upload files via `fal.storage.upload`**: This returns a CDN URL optimized for fal's infrastructure. Direct URLs from other hosts may be slower.
- **Handle queue positions**: Show users their queue position from the status endpoint to set expectations during high-traffic periods.
- **Use named image sizes**: Prefer `square_hd`, `landscape_16_9` over custom dimensions. Named sizes are optimized for each model.
- **Set seeds for reproducibility**: Store seeds alongside outputs to enable deterministic regeneration.
- **Synchronous calls for batch processing**: Use queue submission with webhooks for bulk generation. Synchronous calls tie up your server threads.
- **Ignoring the safety checker**: Disabling `enable_safety_checker` may produce content that violates your platform's terms. Keep it enabled unless you have content moderation downstream.
- **Polling too frequently**: The default poll interval is fine. Setting it below 500ms wastes API calls and may trigger rate limits.
- **Not downloading images promptly**: fal.ai CDN URLs are temporary. Download and persist images to your own storage within the URL's TTL.

## Quick Example

```typescript
import { fal } from "@fal-ai/client";

fal.config({
  credentials: process.env.FAL_KEY,
});
```
skilldb get image-generation-services-skills/fal.ai Image GenerationFull skill: 329 lines
Paste into your CLAUDE.md or agent config

fal.ai Image Generation

Core Philosophy

fal.ai is a serverless GPU inference platform optimized for speed. It runs popular image generation models like Flux with extremely low latency through optimized infrastructure and model caching. The platform offers three execution modes: synchronous (blocking), queue-based (async with polling or webhooks), and realtime (WebSocket streaming). The JavaScript SDK provides first-class TypeScript support with type-safe model inputs and outputs. fal.ai charges per request based on model and resolution, with no idle GPU costs. Choose fal when latency matters most -- it consistently delivers the fastest Flux inference available.

Setup

Install the fal.ai client SDK:

import { fal } from "@fal-ai/client";

fal.config({
  credentials: process.env.FAL_KEY,
});

Set FAL_KEY from your fal.ai dashboard. The SDK handles authentication, request formatting, and response parsing automatically.

For server-side proxy setup to protect your API key in browser environments:

// Next.js API route: app/api/fal/proxy/route.ts
import { route } from "@fal-ai/server-proxy/nextjs";

export const { GET, POST } = route;

// Client-side configuration
import { fal } from "@fal-ai/client";

fal.config({
  proxyUrl: "/api/fal/proxy",
});

Key Techniques

Text-to-Image with Flux

interface FluxResult {
  images: Array<{
    url: string;
    width: number;
    height: number;
    content_type: string;
  }>;
  seed: number;
  prompt: string;
}

async function generateFluxSchnell(
  prompt: string,
  options?: {
    imageSize?: "square_hd" | "square" | "landscape_4_3" | "landscape_16_9" | "portrait_4_3" | "portrait_16_9";
    numImages?: number;
    enableSafetyChecker?: boolean;
    seed?: number;
  }
): Promise<FluxResult> {
  const result = await fal.subscribe("fal-ai/flux/schnell", {
    input: {
      prompt,
      image_size: options?.imageSize ?? "landscape_4_3",
      num_images: options?.numImages ?? 1,
      enable_safety_checker: options?.enableSafetyChecker ?? true,
      seed: options?.seed,
    },
  });

  return result.data as FluxResult;
}

// Higher quality with Flux Dev
async function generateFluxDev(prompt: string, numSteps: number = 28): Promise<FluxResult> {
  const result = await fal.subscribe("fal-ai/flux/dev", {
    input: {
      prompt,
      image_size: "landscape_4_3",
      num_inference_steps: numSteps,
      guidance_scale: 3.5,
      num_images: 1,
      enable_safety_checker: true,
    },
  });

  return result.data as FluxResult;
}

Flux Pro for Production Quality

async function generateFluxPro(
  prompt: string,
  options?: {
    imageSize?: { width: number; height: number };
    safetyTolerance?: "1" | "2" | "3" | "4" | "5" | "6";
    seed?: number;
  }
): Promise<FluxResult> {
  const result = await fal.subscribe("fal-ai/flux-pro/v1.1-ultra", {
    input: {
      prompt,
      image_size: options?.imageSize,
      safety_tolerance: options?.safetyTolerance ?? "2",
      seed: options?.seed,
      raw: false,
    },
  });

  return result.data as FluxResult;
}

Queue API for Async Processing

The queue API is ideal for batch processing or when you need to submit jobs and retrieve results later:

async function submitToQueue(prompt: string): Promise<string> {
  const { request_id } = await fal.queue.submit("fal-ai/flux/schnell", {
    input: {
      prompt,
      image_size: "square_hd",
      num_images: 1,
    },
    webhookUrl: "https://your-server.com/webhooks/fal",
  });

  return request_id;
}

async function checkQueueStatus(requestId: string): Promise<{
  status: "IN_QUEUE" | "IN_PROGRESS" | "COMPLETED";
  queuePosition?: number;
}> {
  const status = await fal.queue.status("fal-ai/flux/schnell", {
    requestId,
    logs: true,
  });

  return {
    status: status.status,
    queuePosition: status.queue_position,
  };
}

async function getQueueResult(requestId: string): Promise<FluxResult> {
  const result = await fal.queue.result("fal-ai/flux/schnell", {
    requestId,
  });

  return result.data as FluxResult;
}

Subscribe with Progress Tracking

async function generateWithProgress(
  prompt: string,
  onProgress?: (update: { status: string; logs?: string[] }) => void
): Promise<FluxResult> {
  const result = await fal.subscribe("fal-ai/flux/dev", {
    input: {
      prompt,
      image_size: "square_hd",
      num_inference_steps: 28,
    },
    pollInterval: 1000,
    logs: true,
    onQueueUpdate: (update) => {
      if (onProgress) {
        onProgress({
          status: update.status,
          logs: update.logs?.map((l) => l.message),
        });
      }
    },
  });

  return result.data as FluxResult;
}

Realtime Image Generation via WebSocket

For interactive applications where you need near-instant image generation:

import { fal } from "@fal-ai/client";

function setupRealtimeGeneration(
  onResult: (imageUrl: string) => void
): {
  send: (prompt: string) => void;
  close: () => void;
} {
  const connection = fal.realtime.connect("fal-ai/flux/schnell", {
    onResult: (result) => {
      const images = result.images as Array<{ url: string }>;
      if (images.length > 0) {
        onResult(images[0].url);
      }
    },
    onError: (error) => {
      console.error("Realtime error:", error);
    },
  });

  return {
    send: (prompt: string) => {
      connection.send({
        prompt,
        image_size: "square",
        num_images: 1,
        sync_mode: true,
      });
    },
    close: () => connection.close(),
  };
}

// Usage in a React-like context
const rt = setupRealtimeGeneration((url) => {
  document.getElementById("preview")!.setAttribute("src", url);
});

// Send prompts as user types (debounced)
rt.send("a cat wearing a top hat");

Image-to-Image with Flux

async function fluxImg2Img(
  imageUrl: string,
  prompt: string,
  strength: number = 0.75
): Promise<FluxResult> {
  const result = await fal.subscribe("fal-ai/flux/dev/image-to-image", {
    input: {
      image_url: imageUrl,
      prompt,
      strength,
      num_inference_steps: 28,
      image_size: "square_hd",
    },
  });

  return result.data as FluxResult;
}

File Uploads

async function uploadAndGenerate(
  filePath: string,
  prompt: string
): Promise<FluxResult> {
  // Upload file to fal's CDN
  const uploadedUrl = await fal.storage.upload(
    new File([await fs.promises.readFile(filePath)], "input.png", {
      type: "image/png",
    })
  );

  // Use uploaded URL for img2img
  return fluxImg2Img(uploadedUrl, prompt);
}

Webhook Handler

import express from "express";

const app = express();

app.post("/webhooks/fal", express.json(), async (req, res) => {
  const { request_id, status, payload } = req.body;

  if (status === "OK") {
    const images = payload.images as Array<{ url: string }>;
    console.log(`Request ${request_id} completed with ${images.length} images`);

    // Download and store images
    for (const image of images) {
      const response = await fetch(image.url);
      const buffer = Buffer.from(await response.arrayBuffer());
      await fs.promises.writeFile(`output-${request_id}.png`, buffer);
    }
  } else {
    console.error(`Request ${request_id} failed:`, payload);
  }

  res.sendStatus(200);
});

Best Practices

  • Use fal.subscribe over raw queue calls: The subscribe method handles polling and status checking automatically with configurable intervals.
  • Choose the right Flux variant: Schnell for speed (4 steps), Dev for quality (28 steps), Pro for production-grade output. Match the model to your latency and quality requirements.
  • Use the proxy in browser apps: Never expose your FAL_KEY client-side. Set up the Next.js or Express proxy to keep credentials server-side.
  • Leverage realtime for interactive UIs: The WebSocket connection eliminates HTTP overhead for rapid iteration workflows like prompt experimentation.
  • Upload files via fal.storage.upload: This returns a CDN URL optimized for fal's infrastructure. Direct URLs from other hosts may be slower.
  • Handle queue positions: Show users their queue position from the status endpoint to set expectations during high-traffic periods.
  • Use named image sizes: Prefer square_hd, landscape_16_9 over custom dimensions. Named sizes are optimized for each model.
  • Set seeds for reproducibility: Store seeds alongside outputs to enable deterministic regeneration.

Anti-Patterns

  • Synchronous calls for batch processing: Use queue submission with webhooks for bulk generation. Synchronous calls tie up your server threads.
  • Ignoring the safety checker: Disabling enable_safety_checker may produce content that violates your platform's terms. Keep it enabled unless you have content moderation downstream.
  • Polling too frequently: The default poll interval is fine. Setting it below 500ms wastes API calls and may trigger rate limits.
  • Not downloading images promptly: fal.ai CDN URLs are temporary. Download and persist images to your own storage within the URL's TTL.
  • Using realtime for batch jobs: WebSocket connections are for interactive, single-image generation. Use the queue API for batch workloads.
  • Hardcoding model endpoints: Store model identifiers in configuration. fal.ai frequently adds new model versions and you should be able to switch without code changes.
  • Large payloads over WebSocket: The realtime connection is optimized for lightweight prompts. Send image data via fal.storage.upload and pass the URL instead.

Install this skill directly: skilldb add image-generation-services-skills

Get CLI access →