---
# === IDENTITY ===
id: software/devops/cloudflare-workers-setup/2026
canonical_question: "Cloudflare Workers setup reference"
aliases:
  - "How to set up Cloudflare Workers with wrangler"
  - "wrangler.toml configuration reference"
  - "Cloudflare Workers KV R2 D1 bindings setup"
  - "Deploy serverless functions on Cloudflare edge"
entity_type: software_reference
domain: software > devops > Cloudflare Workers Setup
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === VERIFICATION ===
last_verified: 2026-02-28
confidence: 0.93
version: 1.0
first_published: 2026-02-28

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "Wrangler v3 (2024) — new config format, module workers default"
  next_review: 2026-08-27
  change_sensitivity: medium

# === CONSTRAINTS ===
constraints:
  - "Worker CPU time limit: 10 ms (free) / 30 s (paid) per invocation — not wall-clock time"
  - "Worker size limit: 10 MB compressed after bundling (paid plan), 1 MB (free)"
  - "KV write rate limit: 1 write per second per key"
  - "D1 database max size: 10 GB per database"
  - "Subrequest limit: 1,000 operations per invocation (across KV, R2, fetch, etc.)"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "You need Cloudflare Pages Functions (static site + API routes)"
    use_instead: "Cloudflare Pages Functions guide"
  - condition: "You need a traditional server with persistent connections (WebSockets >30 min)"
    use_instead: "Cloudflare Durable Objects reference"

# === AGENT HINTS ===
inputs_needed:
  - key: "storage_needs"
    question: "What kind of storage do you need?"
    type: choice
    options: ["Key-value (KV)", "SQL database (D1)", "Object storage (R2)", "None"]
  - key: "plan"
    question: "Are you on the free or paid Workers plan?"
    type: choice
    options: ["Free", "Workers Paid ($5/mo)"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/devops/cloudflare-workers-setup/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-02-28)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: software/devops/caddy-server-config/2026
      label: "Caddy Server Configuration Reference"
  solves: []
  alternative_to:
    - id: software/devops/aws-lambda-sam/2026
      label: "AWS Lambda + API Gateway (SAM)"
  often_confused_with: []

# === SOURCES ===
sources:
  - id: src1
    title: "Wrangler Configuration Reference"
    author: Cloudflare
    url: https://developers.cloudflare.com/workers/wrangler/configuration/
    type: official_docs
    published: 2025-06-01
    reliability: authoritative
  - id: src2
    title: "Wrangler Commands Reference"
    author: Cloudflare
    url: https://developers.cloudflare.com/workers/wrangler/commands/
    type: official_docs
    published: 2025-06-01
    reliability: authoritative
  - id: src3
    title: "Workers Platform Limits"
    author: Cloudflare
    url: https://developers.cloudflare.com/workers/platform/limits/
    type: official_docs
    published: 2025-06-01
    reliability: authoritative
  - id: src4
    title: "Workers Best Practices"
    author: Cloudflare
    url: https://developers.cloudflare.com/workers/best-practices/workers-best-practices/
    type: official_docs
    published: 2026-02-15
    reliability: authoritative
  - id: src5
    title: "Environment Variables"
    author: Cloudflare
    url: https://developers.cloudflare.com/workers/configuration/environment-variables/
    type: official_docs
    published: 2025-06-01
    reliability: authoritative
  - id: src6
    title: "Storage Options Guide"
    author: Cloudflare
    url: https://developers.cloudflare.com/workers/platform/storage-options/
    type: official_docs
    published: 2025-06-01
    reliability: authoritative
  - id: src7
    title: "Workers KV Documentation"
    author: Cloudflare
    url: https://developers.cloudflare.com/kv/
    type: official_docs
    published: 2025-06-01
    reliability: authoritative
---

# Cloudflare Workers Setup Reference

## TL;DR

- **Bottom line**: Cloudflare Workers run JavaScript/TypeScript at the edge in 300+ data centers with sub-millisecond cold starts — configure everything in `wrangler.toml`, deploy with `npx wrangler deploy`.
- **Key tool/command**: `npm create cloudflare@latest && npx wrangler deploy`
- **Watch out for**: CPU time limit (10 ms free / 30 s paid) is NOT wall-clock time — a `fetch()` that takes 2 seconds still only uses milliseconds of CPU. But heavy JSON parsing or crypto ops will hit the limit.
- **Works with**: Wrangler v3+, Node.js 18+, TypeScript out of the box, bindings to KV, R2, D1, Queues, Durable Objects.

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- CPU time limit: 10 ms (free) / 30 s (paid) per invocation — I/O wait does not count.
- Worker script size: 10 MB compressed (paid), 1 MB (free) — includes all bundled dependencies.
- KV: 1 write/second per key, values up to 25 MB, keys up to 512 bytes.
- D1: max 10 GB per database, 6 simultaneous connections per invocation.
- Subrequests: max 1,000 per invocation (fetch, KV, R2, D1 operations combined).
- No raw TCP/UDP sockets — use `connect()` for TCP or Cloudflare Spectrum for proxying.

## Quick Reference

| Setting | Value | Notes |
|---|---|---|
| `name` | Worker name | Required — used as subdomain: `name.account.workers.dev` |
| `main` | `src/index.ts` | Entry point — module format (ES modules default) |
| `compatibility_date` | `2026-02-28` | Required — controls runtime behavior flags |
| `compatibility_flags` | `["nodejs_compat_v2"]` | Enables Node.js built-in module polyfills |
| `[vars]` | key-value pairs | Non-secret env vars — visible in wrangler.toml |
| `[[kv_namespaces]]` | `binding`, `id` | KV namespace binding |
| `[[r2_buckets]]` | `binding`, `bucket_name` | R2 object storage binding |
| `[[d1_databases]]` | `binding`, `database_id` | D1 SQLite database binding |
| `[env.staging]` | Environment overrides | Deploy with `--env staging` |
| `routes` | `[{pattern, zone_name}]` | Custom domain routing |
| `workers_dev` | `true` (default) | Enables `*.workers.dev` subdomain |
| `logpush` | `true` | Enables Workers Logpush (paid) |
| `placement.mode` | `smart` | Smart placement — runs closer to backend |
| `[build]` | `command`, `cwd` | Custom build command before deploy |

## Decision Tree

```
START
├── Need key-value storage with global replication?
│   ├── YES → Use KV (eventually consistent, read-optimized)
│   └── NO ↓
├── Need relational queries (SQL)?
│   ├── YES → Use D1 (SQLite at the edge)
│   └── NO ↓
├── Need to store files/blobs (>25 MB)?
│   ├── YES → Use R2 (S3-compatible, no egress fees)
│   └── NO ↓
├── Need strong consistency / coordination?
│   ├── YES → Use Durable Objects (single-instance, stateful)
│   └── NO ↓
├── Need background processing / message queues?
│   ├── YES → Use Queues (at-least-once delivery)
│   └── NO ↓
├── Need scheduled tasks?
│   ├── YES → Use Cron Triggers ([triggers] in wrangler.toml)
│   └── NO ↓
└── DEFAULT → Plain Worker with fetch handler, no storage
```

## Step-by-Step Guide

### 1. Create a new Workers project

Scaffold a project with the latest Wrangler CLI. [src1]

```bash
npm create cloudflare@latest my-worker
cd my-worker
```

**Verify**: `ls wrangler.toml src/index.ts` → both files exist

### 2. Configure wrangler.toml

Set up the Worker with bindings for KV and environment variables. [src1] [src5]

```toml
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2026-02-28"
compatibility_flags = ["nodejs_compat_v2"]

[vars]
API_BASE = "https://api.example.com"
ENVIRONMENT = "production"

[[kv_namespaces]]
binding = "CACHE"
id = "abc123def456"

[[r2_buckets]]
binding = "UPLOADS"
bucket_name = "my-uploads"

[[d1_databases]]
binding = "DB"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

[env.staging]
name = "my-worker-staging"
vars = { ENVIRONMENT = "staging" }

[[env.staging.kv_namespaces]]
binding = "CACHE"
id = "staging-kv-id-here"
```

**Verify**: `npx wrangler whoami` → shows your Cloudflare account

### 3. Write the Worker handler

Create a module Worker with typed bindings. [src4]

```typescript
// src/index.ts
export interface Env {
  CACHE: KVNamespace;
  UPLOADS: R2Bucket;
  DB: D1Database;
  API_BASE: 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 Response.json({ status: 'ok', env: env.ENVIRONMENT });
    }

    if (url.pathname === '/items' && request.method === 'GET') {
      // Check KV cache first
      const cached = await env.CACHE.get('items', 'json');
      if (cached) {
        return Response.json(cached, {
          headers: { 'X-Cache': 'HIT' }
        });
      }

      // Query D1
      const { results } = await env.DB.prepare(
        'SELECT * FROM items ORDER BY created_at DESC LIMIT 50'
      ).all();

      // Cache for 60 seconds
      ctx.waitUntil(
        env.CACHE.put('items', JSON.stringify(results), { expirationTtl: 60 })
      );

      return Response.json(results, {
        headers: { 'X-Cache': 'MISS' }
      });
    }

    return new Response('Not Found', { status: 404 });
  },
} satisfies ExportedHandler<Env>;
```

**Verify**: `npx wrangler dev` → starts local dev server at `localhost:8787`

### 4. Set secrets

Add sensitive values that should not be in wrangler.toml. [src5]

```bash
# Set a secret (prompts for value interactively)
npx wrangler secret put API_KEY

# Set a secret non-interactively
echo "sk-abc123" | npx wrangler secret put API_KEY

# List existing secrets
npx wrangler secret list
```

**Verify**: `npx wrangler secret list` → shows `API_KEY` in output

### 5. Create storage resources

Create KV namespaces, D1 databases, and R2 buckets. [src6] [src7]

```bash
# Create KV namespace
npx wrangler kv namespace create CACHE
# Output: id = "abc123..." — paste into wrangler.toml

# Create D1 database
npx wrangler d1 create my-database
# Output: database_id = "xxx..." — paste into wrangler.toml

# Create R2 bucket
npx wrangler r2 bucket create my-uploads

# Run D1 migrations
npx wrangler d1 migrations apply my-database
```

**Verify**: `npx wrangler kv namespace list` → shows CACHE namespace

### 6. Deploy to production

Deploy the Worker to Cloudflare's edge network. [src2]

```bash
# Deploy to production
npx wrangler deploy

# Deploy to staging environment
npx wrangler deploy --env staging

# Deploy with dry run (see what would happen)
npx wrangler deploy --dry-run
```

**Verify**: `curl https://my-worker.your-account.workers.dev/health` → returns `{"status":"ok"}`

## Code Examples

### TypeScript: REST API with D1 and KV caching

```typescript
// Input:  HTTP request to /api/posts/:id
// Output: JSON response with post data, cached in KV

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const url = new URL(request.url);
    const match = url.pathname.match(/^\/api\/posts\/(\d+)$/);

    if (!match) return new Response('Not Found', { status: 404 });

    const postId = match[1];
    const cacheKey = `post:${postId}`;

    // Try KV cache
    const cached = await env.CACHE.get(cacheKey, 'json');
    if (cached) return Response.json(cached);

    // Query D1
    const post = await env.DB.prepare(
      'SELECT id, title, body, created_at FROM posts WHERE id = ?'
    ).bind(postId).first();

    if (!post) return Response.json({ error: 'Not found' }, { status: 404 });

    // Cache in background (don't block response)
    ctx.waitUntil(
      env.CACHE.put(cacheKey, JSON.stringify(post), { expirationTtl: 300 })
    );

    return Response.json(post);
  }
} satisfies ExportedHandler<Env>;
```

### TypeScript: R2 file upload with presigned URL pattern

> Full script: [typescript-r2-file-upload-with-presigned-url-patte.ts](scripts/typescript-r2-file-upload-with-presigned-url-patte.ts) (32 lines)

```typescript
// Input:  PUT /upload/:filename with binary body
// Output: Stores file in R2, returns metadata
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
# ... (see full script)
```

## Anti-Patterns

### Wrong: Destructuring `ctx` (loses `this` binding)

```typescript
// BAD — destructuring ctx causes "Illegal invocation"
export default {
  async fetch(request, env, ctx) {
    const { waitUntil } = ctx;
    waitUntil(somePromise); // Throws: Illegal invocation
  }
};
```

### Correct: Call methods on `ctx` directly

```typescript
// GOOD — keep ctx intact
export default {
  async fetch(request, env, ctx) {
    ctx.waitUntil(somePromise); // Works correctly
  }
};
```

### Wrong: Using Service Worker syntax (legacy)

```javascript
// BAD — legacy service worker syntax, no longer recommended
addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request));
});
```

### Correct: Use ES Module syntax (modern)

```typescript
// GOOD — module syntax with typed env
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    return new Response('Hello');
  }
} satisfies ExportedHandler<Env>;
```

### Wrong: Storing secrets in wrangler.toml

```toml
# BAD — secrets visible in version control
[vars]
DATABASE_PASSWORD = "super-secret-123"
API_KEY = "sk-live-abc123"
```

### Correct: Use `wrangler secret` for sensitive values

```bash
# GOOD — secrets stored encrypted, never in code
npx wrangler secret put DATABASE_PASSWORD
npx wrangler secret put API_KEY
```

### Wrong: Blocking the response to do background work

```typescript
// BAD — user waits for analytics write
export default {
  async fetch(request, env) {
    const data = await getData(env);
    await env.DB.prepare('INSERT INTO analytics ...').run(); // Blocks response
    return Response.json(data);
  }
};
```

### Correct: Use `ctx.waitUntil()` for non-critical work

```typescript
// GOOD — response returns immediately, analytics runs in background
export default {
  async fetch(request, env, ctx) {
    const data = await getData(env);
    ctx.waitUntil(env.DB.prepare('INSERT INTO analytics ...').run());
    return Response.json(data);
  }
};
```

## Common Pitfalls

- **Exceeding 1,000 subrequests per invocation**: Each KV get, R2 read, D1 query, and fetch() counts. Fix: batch operations, use `Promise.all()` for parallel requests. [src3]
- **KV eventual consistency surprises**: KV writes take up to 60 seconds to propagate globally. Fix: use D1 or Durable Objects for strong consistency. [src7]
- **Forgetting `compatibility_date`**: Without it, runtime defaults to oldest behavior. Fix: set `compatibility_date` to today's date in wrangler.toml. [src1]
- **Using Node.js APIs without `nodejs_compat_v2`**: Buffer, crypto, and other Node built-ins are unavailable by default. Fix: add `compatibility_flags = ["nodejs_compat_v2"]`. [src4]
- **Large Worker bundle size**: Including unnecessary dependencies. Fix: use tree-shakeable ESM imports, check bundle with `npx wrangler deploy --dry-run --outdir dist`. [src3]
- **D1 large batch operations timing out**: Single UPDATE/DELETE on 100K+ rows exceeds limits. Fix: batch operations in chunks of 1,000 rows. [src6]

## Diagnostic Commands

```bash
# Check Wrangler version and auth status
npx wrangler --version && npx wrangler whoami

# Start local dev server with real bindings
npx wrangler dev --remote

# Tail production logs in real-time
npx wrangler tail

# List all deployed Workers
npx wrangler deployments list

# Check KV namespace contents
npx wrangler kv key list --namespace-id=YOUR_NAMESPACE_ID

# Run D1 query directly
npx wrangler d1 execute my-database --command "SELECT count(*) FROM items"

# Check Worker metrics
npx wrangler metrics
```

## Version History & Compatibility

| Version | Status | Breaking Changes | Migration Notes |
|---|---|---|---|
| Wrangler v3 | Current | Module Workers default, new config format | Migrate from Service Worker syntax |
| Wrangler v2 | Deprecated | — | Run `npx wrangler@3 init` to migrate |
| Module Workers | Current | ES Module export syntax required | Replace `addEventListener` with default export |
| Service Worker syntax | Legacy | — | Still works but not recommended |
| `nodejs_compat_v2` | Current | Replaces `nodejs_compat` | Use v2 flag for better polyfill coverage |
| D1 | GA (2024) | — | Previously beta — now production-ready |
| R2 | GA (2023) | — | S3-compatible API |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Low-latency edge compute (<50 ms response) | Long-running tasks >30 s CPU time | AWS Lambda, Cloud Run |
| Global distribution needed (300+ PoPs) | Heavy CPU computation (ML inference) | GPU-enabled cloud functions |
| Simple key-value or SQL storage | Large relational databases (>10 GB) | Managed PostgreSQL, PlanetScale |
| File storage with no egress fees | Need POSIX filesystem | EC2, Cloud VMs |
| Cost-sensitive (<$5/mo for most apps) | WebSocket connections >30 min | Durable Objects |

## Important Caveats

- `compatibility_date` is a one-way ratchet — once you advance it, you cannot go back without potentially breaking behavior changes. Test thoroughly before updating.
- Workers cannot make outbound connections to non-standard ports by default — only ports 80 and 443 for HTTP/HTTPS.
- KV `list()` operations are eventually consistent — newly created keys may not appear immediately in list results.
- D1 is single-region (not globally replicated like KV) — read latency depends on which region your database is in. Use Smart Placement to co-locate the Worker with D1.

## Related Units

- [AWS Lambda + API Gateway (SAM)](/software/devops/aws-lambda-sam/2026)
- [Caddy Server Configuration Reference](/software/devops/caddy-server-config/2026)
