Cloud Native 9 min read

Build a Free Flux Text-to-Image API on Cloudflare in 5 Minutes

This guide shows how to use Cloudflare Workers AI's free daily quota to quickly create a custom Flux‑1‑Schnell text‑to‑image API, covering project initialization, AI binding configuration, request validation, error handling, authentication, deployment, and testing with curl.

Full-Stack Cultivation Path
Full-Stack Cultivation Path
Full-Stack Cultivation Path
Build a Free Flux Text-to-Image API on Cloudflare in 5 Minutes

Cloudflare offers a generous free daily quota for Workers AI, which is sufficient for low‑frequency usage. Leveraging this, the article walks through building a dedicated Flux‑1‑Schnell text‑to‑image API that can be integrated into an MCP Server for unrestricted image generation.

Prerequisites

Node.js installed locally

A Cloudflare account

Project Initialization

Run the following command to create a new Cloudflare Workers project: pnpm create cloudflare@latest cf-flux-schnell Follow the prompts to select a starter template and programming language (TypeScript).

Configure Workers AI

Add an AI binding in wrangler.jsonc:

{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "cf-flux-schnell",
  "main": "src/index.ts",
  "compatibility_date": "2025-03-12",
  "observability": { "enabled": true },
  "ai": { "binding": "AI" }
}

Understand the Flux‑1‑Schnell API

The official schema defines the input and output objects. The input requires a prompt string (1‑2048 characters) and an optional steps integer (1‑8, default 4). The output returns an image field containing a Base64‑encoded JPEG.

{
  "type": "object",
  "properties": {
    "prompt": { "type": "string", "minLength": 1, "maxLength": 2048, "description": "A text description of the image you want to generate." },
    "steps": { "type": "integer", "default": 4, "maximum": 8, "description": "The number of diffusion steps; higher values can improve quality but take longer." }
  },
  "required": ["prompt"]
}

Develop the Flux‑1‑Schnell API

1. Add request validation using the zod library:

pnpm add zod
import { z } from 'zod';

const GenerateImageSchema = z.object({
  prompt: z.string().min(1, 'Prompt cannot be empty').max(2048, 'Prompt must not exceed 2048 characters'),
  steps: z.number().int().min(1).max(8).default(4).optional()
});

2. Define custom error classes for validation, unauthorized access, and method not allowed:

export class ApiError extends Error {
  constructor(public status: number, message: string, public details?: unknown) {
    super(message);
    this.name = 'ApiError';
  }
}
export class ValidationError extends ApiError {
  constructor(details: z.ZodError) {
    super(400, 'Validation error', details.errors);
    this.name = 'ValidationError';
  }
}
export class UnauthorizedError extends ApiError {
  constructor() { super(401, 'Unauthorized'); this.name = 'UnauthorizedError'; }
}
export class MethodNotAllowedError extends ApiError {
  constructor() { super(405, 'Method not allowed'); this.name = 'MethodNotAllowedError'; }
}

3. Enforce POST method and Authorization header :

const FLUX_TOKEN = 'Y2YtZmx1eC1hcGk=';

async function validateRequest(request: Request): Promise<z.infer<typeof GenerateImageSchema>> {
  if (request.method !== 'POST') throw new MethodNotAllowedError();
  const authHeader = request.headers.get('Authorization');
  if (!authHeader || authHeader !== `Bearer ${FLUX_TOKEN}`) throw new UnauthorizedError();
  const body = await request.json();
  try { return GenerateImageSchema.parse(body); }
  catch (error) {
    if (error instanceof z.ZodError) throw new ValidationError(error);
    throw new ApiError(400, 'Invalid request body');
  }
}

4. Utility functions for JSON responses and error handling :

const createJsonResponse = (data: unknown, status = 200): Response =>
  new Response(JSON.stringify(data), { status, headers: { 'Content-Type': 'application/json' } });

const handleError = (error: unknown): Response => {
  if (error instanceof ApiError) {
    return createJsonResponse({ error: error.message, details: error.details ?? undefined }, error.status);
  }
  return createJsonResponse({ error: 'Internal server error', details: error instanceof Error ? error.message : 'Unknown error' }, 500);
};

5. Update the fetch handler to validate input, call the AI model, and return the image data URI:

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    try {
      const validatedData = await validateRequest(request);
      const response = await env.AI.run('@cf/black-forest-labs/flux-1-schnell', {
        prompt: validatedData.prompt,
        steps: validatedData.steps,
      });
      const result = { image: response.image ? `data:image/png;base64,${response.image}` : '' };
      return createJsonResponse(result);
    } catch (error) {
      return handleError(error);
    }
  },
} satisfies ExportedHandler<Env>;

Testing and Deployment

1. Run the local development server: pnpm dev 2. Deploy to Cloudflare Workers: pnpm run deploy 3. Test the deployed endpoint with curl (replace the URL with your worker's domain):

curl --request POST \
  --url http://your-site.workers.dev/ \
  --header 'Authorization: Bearer Y2YtZmx1eC1hcGk=' \
  --header 'Content-Type: application/json' \
  --data '{
    "prompt": "1girl, cute, drinking water, glass cup, morning light, cartoon style, simple background",
    "steps": 4
  }'

After a successful response, the image field contains a data URI that can be used directly in an <img> tag.

https://gist.github.com/bytefer/6ea44e54c37c276b9c2cca4244ec32e2

The complete source code is available in the linked Gist.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

TypeScriptText-to-ImageAI modelzodCloudflare WorkersFlux SchnellServerless API
Full-Stack Cultivation Path
Written by

Full-Stack Cultivation Path

Focused on sharing practical tech content about TypeScript, Vue 3, front-end architecture, and source code analysis.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.