Overview
Workers run JavaScript, TypeScript, and WebAssembly in V8 isolates on Cloudflare’s network, close to the caller, with millisecond-scale startup and no container to boot. Choose them for short, latency-sensitive, mostly stateless work; choose a server when you need long CPU jobs, gigabytes of memory, or a long-lived database connection.
Plan around these limits and prices
Figures from the limits and pricing pages, checked October 2026.
| Free | Paid ($5/month per account) | |
|---|---|---|
| Requests | 100,000 per day | 10 million per month, then $0.30 per million |
| CPU time per invocation | 10 ms | 30 s default, up to 5 min |
| CPU billing | none | 30 million CPU-ms included, then $0.02 per million |
| Subrequests per request | 50 | 10,000 (up to 10 million) |
| Memory per isolate | 128 MB | 128 MB |
| Worker size | 64 MiB uncompressed | 64 MiB uncompressed |
Duration (wall-clock) is not billed on either plan; CPU time is. There is no compressed-size limit, only the uncompressed bundle size that Wrangler prints as Total Upload. Startup must finish within 1 second, so avoid heavy work at module scope.
Export a fetch handler and use ctx.waitUntil for background work
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
const url = new URL(request.url)
if (url.pathname === "/health") return new Response("ok")
ctx.waitUntil(logHit(env, url.pathname)) // finishes after the response is sent
return new Response("Hello from the edge")
},
async scheduled(controller: ScheduledController, env: Env, ctx: ExecutionContext) {
// runs on the cron in the config
},
}env holds the bindings and secrets. Add a scheduled handler plus "triggers": { "crons": ["0 * * * *"] } for periodic jobs without a server.
Declare bindings in the Wrangler config
Cloudflare recommends wrangler.jsonc for new projects, and some newer Wrangler features exist only in the JSON format. Set compatibility_date to the day you create the project.
{
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2026-10-01",
"kv_namespaces": [{ "binding": "CACHE", "id": "<namespace-id>" }],
"r2_buckets": [{ "binding": "ASSETS", "bucket_name": "site-assets" }],
"d1_databases": [{ "binding": "DB", "database_name": "app", "database_id": "<id>" }],
"queues": { "producers": [{ "binding": "JOBS", "queue": "jobs" }] }
}Pick by access pattern: cloudflare-kv for read-heavy lookups, cloudflare-r2 for blobs, D1 for relational data (10 GB per database on paid, 500 MB on free), Queues for asynchronous fanout, cloudflare-durable-objects for single-writer state and coordination. Workers have no local disk; outbound TCP uses the connect() API.
Check Node compatibility before importing packages
Workers use Web APIs (fetch, Request, crypto.subtle). Node APIs such as Buffer, crypto, and streams are available through the nodejs_compat flag: for compatibility dates of 2026-08-04 or later it is on by default, and earlier dates need "compatibility_flags": ["nodejs_compat"]. Some modules (node:http2, node:vm, node:cluster) import but do not work (Node.js APIs).
Develop and deploy with Wrangler
npm i -D wrangler # pin the version in the lockfile
npx wrangler dev # local runtime, port 8787, emulated bindings
npx wrangler dev --remote # real edge and real bindings; use to debug quota and consistency
npx wrangler deploy
npx wrangler tail # stream logs from the live WorkerPick Workers when latency drives the design
Good fits: auth gates, A/B routing, geo redirects, bot filtering, APIs that read KV or R2 and return small JSON, webhook receivers that enqueue and return fast, and cron jobs. Move heavy CPU, large in-memory data, or Node-only native libraries to a VPS (hostinger-vps) or vercel Node functions. Stream large bodies with TransformStream instead of buffering them against the 128 MB cap.