Skip to main content
Technology & EngineeringCloudflare Workers354 lines

Workers Fundamentals

Cloudflare Workers runtime fundamentals including V8 isolates, wrangler CLI, project setup, local development, deployment, environment variables, secrets, and compatibility dates.

Quick Summary30 lines
You are an expert in Cloudflare Workers, the serverless edge compute platform that runs JavaScript, TypeScript, and WebAssembly on Cloudflare's global network using V8 isolates.

## Key Points

- **CPU time limits**: Free plan gets 10 ms CPU time per invocation; paid plan gets 30 seconds.
- **Memory**: 128 MB per isolate.
- **No Node.js built-ins by default**: Workers use Web Standard APIs (fetch, crypto, streams, etc.). Node.js compat is opt-in via `nodejs_compat` flag.
- **No file system**: There is no `fs` module. Use KV, R2, or D1 for persistence.
- **Request-scoped execution**: Workers run in response to events (HTTP requests, cron triggers, queue messages).
- `ctx.waitUntil(promise)` — Keeps the Worker alive after returning a response. Use for background tasks like logging, analytics, or cache writes.
- `ctx.passThroughOnException()` — Falls through to origin server if the Worker throws.
- **"exceeded CPU time limit"** — Optimize hot paths, avoid synchronous heavy computation, consider breaking work across multiple requests or using Durable Objects.
- **"exceeded subrequest limit"** — Free plan allows 50 subrequests; paid allows 1000. Batch operations where possible.
- **"compatibility flag required"** — Add the required flag to `compatibility_flags` in `wrangler.toml`.
- **Debugging** — Use `wrangler tail` for live logs, `console.log()` for local dev, and the Cloudflare dashboard for error analytics.

## Quick Example

```bash
npm install -g wrangler
# Or use npx
npx wrangler init my-worker
```

```bash
npx wrangler init my-worker
cd my-worker
```
skilldb get cloudflare-workers-skills/Workers FundamentalsFull skill: 354 lines
Paste into your CLAUDE.md or agent config

Workers Fundamentals — Cloudflare Workers

You are an expert in Cloudflare Workers, the serverless edge compute platform that runs JavaScript, TypeScript, and WebAssembly on Cloudflare's global network using V8 isolates.

Core Philosophy

Overview

Cloudflare Workers run on V8 isolates—not containers or VMs. Each Worker executes in a lightweight V8 isolate that starts in under 5 milliseconds, uses minimal memory, and runs in 300+ data centers worldwide. This architecture gives sub-millisecond cold starts and automatic global distribution without any infrastructure management.

Key constraints

  • CPU time limits: Free plan gets 10 ms CPU time per invocation; paid plan gets 30 seconds.
  • Memory: 128 MB per isolate.
  • No Node.js built-ins by default: Workers use Web Standard APIs (fetch, crypto, streams, etc.). Node.js compat is opt-in via nodejs_compat flag.
  • No file system: There is no fs module. Use KV, R2, or D1 for persistence.
  • Request-scoped execution: Workers run in response to events (HTTP requests, cron triggers, queue messages).

Project Setup

Install wrangler

npm install -g wrangler
# Or use npx
npx wrangler init my-worker

Create a new project

npx wrangler init my-worker
cd my-worker

This generates a wrangler.toml configuration file and a src/index.ts entry point.

wrangler.toml — the configuration file

name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-09-23"
compatibility_flags = ["nodejs_compat"]

# Account-level settings
account_id = "your-account-id"

# Environment variables (non-secret)
[vars]
ENVIRONMENT = "production"
API_BASE_URL = "https://api.example.com"

# KV namespace binding
[[kv_namespaces]]
binding = "MY_KV"
id = "abc123"
preview_id = "def456"

# D1 database binding
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "xxxx-yyyy-zzzz"

# R2 bucket binding
[[r2_buckets]]
binding = "BUCKET"
bucket_name = "my-bucket"

Compatibility dates

The compatibility_date field pins your Worker to a specific set of runtime behaviors. Cloudflare may change default behaviors over time; the compatibility date ensures your Worker is unaffected. Always set this to a recent date when starting a new project and update it periodically.

compatibility_date = "2024-09-23"
compatibility_flags = ["nodejs_compat"]

The Worker Entry Point

ES modules syntax (recommended)

export interface Env {
  MY_KV: KVNamespace;
  DB: D1Database;
  BUCKET: R2Bucket;
  API_KEY: string;
  ENVIRONMENT: string;
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const url = new URL(request.url);

    if (url.pathname === "/health") {
      return new Response("OK", { status: 200 });
    }

    return new Response("Hello from Cloudflare Workers!", {
      headers: { "content-type": "text/plain" },
    });
  },
};

ExecutionContext

The ctx parameter provides:

  • ctx.waitUntil(promise) — Keeps the Worker alive after returning a response. Use for background tasks like logging, analytics, or cache writes.
  • ctx.passThroughOnException() — Falls through to origin server if the Worker throws.
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    // Return response immediately
    const response = new Response("Accepted", { status: 202 });

    // Log analytics in the background without blocking the response
    ctx.waitUntil(
      fetch("https://analytics.example.com/log", {
        method: "POST",
        body: JSON.stringify({ path: new URL(request.url).pathname }),
      })
    );

    return response;
  },
};

Environment Variables and Secrets

Plain variables in wrangler.toml

[vars]
ENVIRONMENT = "staging"
LOG_LEVEL = "debug"

Secrets (sensitive values)

Never put secrets in wrangler.toml. Use the CLI:

# Set a secret
npx wrangler secret put API_KEY
# Prompts for the value interactively

# List secrets
npx wrangler secret list

# Delete a secret
npx wrangler secret delete API_KEY

Secrets are accessed the same way as variables — through the env parameter:

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const apiKey = env.API_KEY; // secret
    const environment = env.ENVIRONMENT; // plain var
    // ...
  },
};

Per-environment configuration

name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-09-23"

[vars]
ENVIRONMENT = "production"

[env.staging]
vars = { ENVIRONMENT = "staging" }

[env.staging.routes]
pattern = "staging.example.com/*"

Deploy to a specific environment:

npx wrangler deploy --env staging

Local Development

wrangler dev

# Start local dev server
npx wrangler dev

# With local persistence for KV, D1, R2
npx wrangler dev --persist-to=.wrangler/state

# Bind to a specific port
npx wrangler dev --port 8787

# Use remote resources instead of local emulation
npx wrangler dev --remote

wrangler dev uses Miniflare under the hood, which faithfully emulates the Workers runtime locally. Local KV, D1, and R2 data persists across restarts when using --persist-to.

Testing with Vitest

// vitest.config.ts
import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config";

export default defineWorkersConfig({
  test: {
    poolOptions: {
      workers: {
        wrangler: { configPath: "./wrangler.toml" },
      },
    },
  },
});
// src/index.test.ts
import { describe, it, expect } from "vitest";
import { SELF } from "cloudflare:test";

describe("Worker", () => {
  it("responds with hello", async () => {
    const response = await SELF.fetch("https://example.com/");
    expect(response.status).toBe(200);
    expect(await response.text()).toBe("Hello from Cloudflare Workers!");
  });
});

Deployment

Deploy to production

# Deploy
npx wrangler deploy

# Deploy with a specific name
npx wrangler deploy --name my-worker-v2

# Tail logs in real time
npx wrangler tail

# View deployment history
npx wrangler deployments list

# Rollback to a previous deployment
npx wrangler rollback

Custom domains and routes

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

# Or use custom domains (simpler, auto-creates DNS records)
[custom_domain]
hostname = "api.example.com"

Common Patterns

JSON API response helper

function json(data: unknown, status = 200): Response {
  return new Response(JSON.stringify(data), {
    status,
    headers: { "content-type": "application/json" },
  });
}

function error(message: string, status = 500): Response {
  return json({ error: message }, status);
}

Reading request body

async function handlePost(request: Request): Promise<Response> {
  const contentType = request.headers.get("content-type") || "";

  if (contentType.includes("application/json")) {
    const body = await request.json();
    return json({ received: body });
  }

  if (contentType.includes("form")) {
    const formData = await request.formData();
    const name = formData.get("name");
    return json({ name });
  }

  const text = await request.text();
  return json({ raw: text });
}

Subrequest fan-out

Workers can make up to 1000 subrequests per invocation (paid plan):

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const urls = [
      "https://api.example.com/users",
      "https://api.example.com/posts",
      "https://api.example.com/comments",
    ];

    const results = await Promise.all(
      urls.map((url) => fetch(url).then((r) => r.json()))
    );

    return json({ users: results[0], posts: results[1], comments: results[2] });
  },
};

Troubleshooting

  • "exceeded CPU time limit" — Optimize hot paths, avoid synchronous heavy computation, consider breaking work across multiple requests or using Durable Objects.
  • "exceeded subrequest limit" — Free plan allows 50 subrequests; paid allows 1000. Batch operations where possible.
  • "compatibility flag required" — Add the required flag to compatibility_flags in wrangler.toml.
  • Debugging — Use wrangler tail for live logs, console.log() for local dev, and the Cloudflare dashboard for error analytics.

Install this skill directly: skilldb add cloudflare-workers-skills

Get CLI access →