Overview

Cloudflare Workers run JavaScript (or WASM) at the edge, globally, with zero cold starts. A Worker handles HTTP requests in a fetch handler, optionally reading from KV for low-latency key-value lookups. The broader platform reference lives in cloudflare-workers; KV specifics are in cloudflare-kv.

Prerequisites

  • A Cloudflare account. Free tier supports Workers with 100,000 daily requests.
  • Node 20+ and npm on the path.
  • Wrangler installed globally: npm install -g wrangler.
  • Authenticated: wrangler login opens the browser OAuth flow.

Steps

1. Initialize the project

npm create cloudflare@latest my-worker
cd my-worker

Choose “Hello World” when prompted for a template. This generates wrangler.toml, src/index.ts, and a minimal tsconfig.json.

Key fields in wrangler.toml:

name = "my-worker"
main = "src/index.ts"
compatibility_date = "2025-01-01"

Set compatibility_date to a recent date to opt into the latest runtime behaviour flags. Do not leave it at the scaffold default from years ago.

2. Write the fetch handler

Every Worker exports a fetch function that receives a Request and returns a Response.

// src/index.ts
export interface Env {
  MY_KV: KVNamespace;
}
 
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
 
    if (url.pathname === "/health") {
      return new Response("ok", { status: 200 });
    }
 
    const key = url.searchParams.get("key");
    if (!key) {
      return new Response("Missing key param", { status: 400 });
    }
 
    const value = await env.MY_KV.get(key);
    if (!value) {
      return new Response("Not found", { status: 404 });
    }
 
    return Response.json({ key, value });
  },
};

Type the Env interface to match the bindings declared in wrangler.toml. The TypeScript compiler catches missing bindings before deployment.

3. Bind KV storage

Create a KV namespace in the Cloudflare dashboard or via wrangler:

wrangler kv namespace create MY_KV
# Outputs: id = "abc123..."
 
wrangler kv namespace create MY_KV --preview
# Outputs: preview_id = "def456..."

Add the binding to wrangler.toml:

[[kv_namespaces]]
binding = "MY_KV"
id = "abc123..."
preview_id = "def456..."

The binding value matches the key in the Env interface. The preview_id is used during local development. See cloudflare-kv for TTL and metadata patterns.

4. Test locally

wrangler dev
# Worker running at http://localhost:8787

In another terminal:

# Write a value to the preview namespace
wrangler kv key put --namespace-id=def456... "hello" "world"
 
# Test the endpoint
curl "http://localhost:8787/?key=hello"
# {"key":"hello","value":"world"}

wrangler dev hot-reloads on file changes. The local runtime matches the production runtime closely; most bugs surface here before deployment.

5. Deploy to production

wrangler deploy
# Uploading my-worker...
# Published my-worker (1.23 sec)
# https://my-worker.your-subdomain.workers.dev

The worker is live globally within seconds. Verify with curl:

curl "https://my-worker.your-subdomain.workers.dev/health"
# ok

To add a custom domain, configure a route in wrangler.toml:

routes = [
  { pattern = "api.example.com/*", zone_name = "example.com" }
]

See cloudflare for DNS setup and vercel-vs-cloudflare for when Workers is the better choice over Vercel Edge Functions.

6. Wire to CI for automated deploys

Add a GitHub Actions step to deploy on push to main. Store CLOUDFLARE_API_TOKEN in GitHub secrets. See github-actions for the full workflow pattern.

- name: Deploy Worker
  run: wrangler deploy --env production
  env:
    CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

Verify it worked

# Health check
curl -si "https://my-worker.your-subdomain.workers.dev/health" | head -5
# HTTP/2 200
 
# KV round-trip
wrangler kv key put --namespace-id=abc123... "ping" "pong"
curl "https://my-worker.your-subdomain.workers.dev/?key=ping"
# {"key":"ping","value":"pong"}
 
# Tail live logs
wrangler tail my-worker

Common errors

  • Error: No account id found. Add account_id = "..." to wrangler.toml or set CLOUDFLARE_ACCOUNT_ID in the environment.
  • KV binding returns undefined. The binding name in wrangler.toml does not match the field name in Env. Both must be identical.
  • compatibility_date warning. The date in wrangler.toml is in the past; update it to within the last 12 months to use current runtime flags.
  • Deploy fails with “script too large”. Workers have a 1 MB compressed script limit. Use dynamic imports or move heavy logic to a Durable Object; see cloudflare-durable-objects.
  • Local wrangler dev differs from production. Some APIs (like crypto.subtle) behave differently in the local runtime. Test with wrangler dev --remote to run against the real edge infrastructure.