AI EngineeringCloudflareStable Diffusion

Deploy a free text-to-image API on Cloudflare Workers

Cloudflare Workers AI gives you 100,000 image generation calls per day on the free tier. I built a simple worker that turns text prompts into images using Stable Diffusion, without a GPU bill.

SaifullahSaifullah
5 min read
Deploy a free text-to-image API on Cloudflare Workers

I kept paying for image API credits on side projects that barely shipped. Then I found Cloudflare Workers AI: a single worker that turns text prompts into images, with up to 100,000 requests per day on the free tier.

No GPU needed. No monthly bill. No credit card required before you can test an idea.

This post is the setup I wish I had the first time through. Write a worker, set two bindings, and you have a real endpoint.

What you actually get

You write a worker.js file. You paste it into a Cloudflare Worker, add an API key as an environment variable, bind Workers AI, and you are done.

PieceWhat it does
Cloudflare WorkerReceives POST requests, checks your API key, forwards the prompt
Workers AIRuns the model (Stable Diffusion XL by default)
Your API keyKeeps random people off your 100k daily quota

The free tier limit is 100,000 AI requests per day. For a personal app, internal tooling, or early prototypes, that is a lot of headroom. Cloudflare's pricing page has the details if you outgrow it.

Before you start

You need:

  • A Cloudflare account (free is fine)
  • Ten minutes and a text editor
  • A secret API key you invent yourself (not your Cloudflare password)

You do not need to write new backend code unless you want to change models or add rate limiting.

Step 1: Create the worker

Open the Workers dashboard and click Create application, then Create Worker.

Name it something like free-image-generation-api. Deploy the default Hello World worker first so the URL exists. You will replace the code in the next step.

Step 2: Paste the worker code

In the worker editor, delete the Hello World snippet and paste your worker code. Here is a simple example:

export default { async fetch(request, env) { if (request.method !== "POST") { return new Response("POST only", { status: 405 }); } const apiKey = request.headers.get("Authorization"); if (apiKey !== `Bearer ${env.API_KEY}`) { return new Response("Invalid API key", { status: 401 }); } const { prompt } = await request.json(); const response = await env.AI.run("@cf/stabilityai/stable-diffusion-xl-base-1.0", { prompt: prompt, }); return new Response(response, { headers: { "content-type": "image/png" }, }); }, };

Click Save and Deploy.

Step 3: Set your API key

Go to Settings > Variables > Environment Variables.

Add one variable:

  • Name: API_KEY
  • Value: a long random string (a password generator works)

This is what clients send as Authorization: Bearer <key>. Treat it like a production secret. Rotate it if it leaks.

Save and deploy again.

Step 4: Enable Workers AI

In the dashboard, open Workers & Pages > AI and enable Workers AI for your account. The free tier is enough for this walkthrough.

If AI is not enabled, every request will fail with an opaque error and you will waste twenty minutes checking your API key for no reason. Ask me how I know.

Step 5: Bind Workers AI to the worker

Back in your worker: Settings > Variables, scroll to Service bindings, click Add binding.

  • Variable name: AI
  • Service: Workers AI

Save and deploy.

⚠️ Without this binding, the worker cannot reach Cloudflare's models. The code expects env.AI. Skip this step and nothing generates, no matter how good your prompt is.

Step 6: Note your URL

Your endpoint looks like:

https://<worker-name>.<your-subdomain>.workers.dev

The dashboard shows the exact URL. That is your image API base.

AI generated image of a cute robot cooking breakfast in a kitchen

Call it from the terminal

Replace the URL and key with yours:

curl -X POST https://your-worker.your-subdomain.workers.dev \ -H "Authorization: Bearer your-secret-api-key" \ -H "Content-Type: application/json" \ -d '{"prompt": "A cute robot cooking breakfast"}' \ --output image.jpg

Open image.jpg. If you see a robot at a stove, the stack works.

Call it from JavaScript

const res = await fetch("https://your-worker.your-subdomain.workers.dev", { method: "POST", headers: { Authorization: "Bearer your-secret-api-key", "Content-Type": "application/json", }, body: JSON.stringify({ prompt: "A futuristic city in the clouds" }), }); const blob = await res.blob(); const img = document.createElement("img"); img.src = URL.createObjectURL(blob); document.body.appendChild(img);

Same pattern in Node if you use fetch or undici. The response body is the image bytes, not JSON.

Swapping models

The default in worker.js targets Stable Diffusion XL. Cloudflare adds and retires models over time, so read the comments in the file and the Workers AI model list before you change the model ID.

Smaller models are faster and cheaper against your daily quota. Larger models look better but burn through 100k faster if you expose the endpoint publicly.

Security notes that matter

  • Never commit your API_KEY to public code repositories.
  • Do not expose the endpoint in frontend code without a proxy. Anyone can read browser network tabs.
  • If you need public access, put the worker behind your own backend or add Cloudflare rate limiting.

For internal tools and local scripts, Bearer auth in the header is fine.

When this is worth it vs paid APIs

I still use paid APIs when I need a specific fine-tuned model, guaranteed SLA, or img2img with tight control. For "generate a thumbnail from a sentence" or "prototype a feature that needs images," this setup has been enough.

100k calls per day is not infinite. A leaked key on a public repo can burn through it. Monitor usage in the Cloudflare dashboard if the endpoint leaves your laptop.

Wrapping up

You now have a self-hosted text-to-image API on Cloudflare's edge: no GPU rental, no per-image invoice for the first 100k daily requests. Deploy the worker, bind AI, and send a prompt.

What would you build with 100k free generations a day?

Share this post