wrangler.toml, deploy with npx wrangler deploy.npm create cloudflare@latest && npx wrangler deploy| Setting | Value | Notes |
|---|---|---|
name | Worker name | Required — used as subdomain |
main | src/index.ts | Entry point (ES modules default) |
compatibility_date | 2026-02-28 | Required — controls runtime flags |
compatibility_flags | ["nodejs_compat_v2"] | Enables Node.js polyfills |
[vars] | key-value pairs | Non-secret env vars |
[[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 |
placement.mode | smart | Runs closer to backend |
[build] | command, cwd | Custom build command |
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?
│ ├── YES → Use Queues (at-least-once delivery)
│ └── NO ↓
└── DEFAULT → Plain Worker with fetch handler, no storage
Scaffold a project with Wrangler CLI. [src1]
npm create cloudflare@latest my-worker
cd my-worker
Verify: ls wrangler.toml src/index.ts → both files exist
Set up bindings for KV, R2, D1. [src1] [src5]
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2026-02-28"
compatibility_flags = ["nodejs_compat_v2"]
[vars]
API_BASE = "https://api.example.com"
[[kv_namespaces]]
binding = "CACHE"
id = "abc123def456"
[[d1_databases]]
binding = "DB"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
Verify: npx wrangler whoami → shows account
Create a module Worker with typed bindings. [src4]
export interface Env {
CACHE: KVNamespace;
DB: D1Database;
}
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
const url = new URL(request.url);
if (url.pathname === '/health') {
return Response.json({ status: 'ok' });
}
return new Response('Not Found', { status: 404 });
}
} satisfies ExportedHandler<Env>;
Verify: npx wrangler dev → starts at localhost:8787
Deploy the Worker to Cloudflare's edge. [src2]
npx wrangler deploy
Verify: curl https://my-worker.your-account.workers.dev/health → {"status":"ok"}
// 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) {
const url = new URL(request.url);
const match = url.pathname.match(/^\/api\/posts\/(\d+)$/);
if (!match) return new Response('Not Found', { status: 404 });
const cacheKey = `post:${match[1]}`;
const cached = await env.CACHE.get(cacheKey, 'json');
if (cached) return Response.json(cached);
const post = await env.DB.prepare(
'SELECT * FROM posts WHERE id = ?'
).bind(match[1]).first();
if (!post) return Response.json({ error: 'Not found' }, { status: 404 });
ctx.waitUntil(env.CACHE.put(cacheKey, JSON.stringify(post), { expirationTtl: 300 }));
return Response.json(post);
}
} satisfies ExportedHandler<Env>;
// Input: PUT /upload/:filename with binary body
// Output: Stores file in R2, returns metadata
export default {
async fetch(request: Request, env: Env) {
if (request.method === 'PUT' && new URL(request.url).pathname.startsWith('/upload/')) {
const filename = new URL(request.url).pathname.replace('/upload/', '');
const object = await env.UPLOADS.put(filename, request.body, {
httpMetadata: { contentType: request.headers.get('content-type') || 'application/octet-stream' }
});
return Response.json({ key: object.key, size: object.size }, { status: 201 });
}
return new Response('Method Not Allowed', { status: 405 });
}
} satisfies ExportedHandler<Env>;
// ❌ BAD — causes "Illegal invocation"
const { waitUntil } = ctx;
waitUntil(somePromise);
// ✅ GOOD — keep ctx intact
ctx.waitUntil(somePromise);
// ❌ BAD — legacy format
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
// ✅ GOOD — module format with typed env
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
return new Response('Hello');
}
} satisfies ExportedHandler<Env>;
# ❌ BAD — visible in version control
[vars]
API_KEY = "sk-live-abc123"
# ✅ GOOD — encrypted, never in code
npx wrangler secret put API_KEY
// ❌ BAD — user waits for analytics write
const data = await getData(env);
await env.DB.prepare('INSERT INTO analytics ...').run();
return Response.json(data);
// ✅ GOOD — response returns immediately
const data = await getData(env);
ctx.waitUntil(env.DB.prepare('INSERT INTO analytics ...').run());
return Response.json(data);
Promise.all(). [src3]compatibility_date to today's date. [src1]compatibility_flags = ["nodejs_compat_v2"]. [src4]--dry-run --outdir dist. [src3]# Check Wrangler version and auth
npx wrangler --version && npx wrangler whoami
# Start local dev with real bindings
npx wrangler dev --remote
# Tail production logs
npx wrangler tail
# List deployments
npx wrangler deployments list
# Check KV contents
npx wrangler kv key list --namespace-id=YOUR_ID
# Run D1 query
npx wrangler d1 execute my-db --command "SELECT count(*) FROM items"
| Version | Status | Breaking Changes | Migration Notes |
|---|---|---|---|
| Wrangler v3 | Current | Module Workers default, new config | Migrate from Service Worker syntax |
| Wrangler v2 | Deprecated | — | Run npx wrangler@3 init |
| nodejs_compat_v2 | Current | Replaces nodejs_compat | Better polyfill coverage |
| D1 | GA (2024) | — | Production-ready |
| R2 | GA (2023) | — | S3-compatible API |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Low-latency edge compute (<50 ms) | Long-running tasks >30 s CPU | AWS Lambda, Cloud Run |
| Global distribution (300+ PoPs) | Heavy CPU (ML inference) | GPU cloud functions |
| Simple KV or SQL storage | Large relational DB (>10 GB) | Managed PostgreSQL |
| File storage with no egress | POSIX filesystem needed | EC2, Cloud VMs |
| Cost-sensitive (<$5/mo) | WebSocket >30 min | Durable Objects |
compatibility_date is a one-way ratchet — once advanced, you cannot go back without potentially breaking changes.list() operations are eventually consistent — newly created keys may not appear immediately.