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.

FreePaid ($5/month per account)
Requests100,000 per day10 million per month, then $0.30 per million
CPU time per invocation10 ms30 s default, up to 5 min
CPU billingnone30 million CPU-ms included, then $0.02 per million
Subrequests per request5010,000 (up to 10 million)
Memory per isolate128 MB128 MB
Worker size64 MiB uncompressed64 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 Worker

Pick 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.