agentleFS
d-padmanabhand-padmanabhan/agent-engineering-handbook/skills/cloudflare-workers-author/SKILL.md

cloudflare-workers-author

Skill17 starsChanged 56 days agoMITView on GitHub

Workflow for building production-grade Cloudflare Workers in TypeScript - bootstrap, bindings and storage choice, routing (Hono + RPC via WorkerEntrypoint), testing with @cloudflare/vitest-plugin, gradual deployments, and common pitfalls. Use when adding a new Worker, adding endpoints to an existing Worker, choosing between KV / R2 / D1 / Durable Objects / Hyperdrive, setting up testing, configuring deployment, or reviewing a Worker PR.

The file

We split this skill into the rules it gives an agent

6 rules. About 0 are common, 2 are distinctive, and 4 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 4

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Do NOT invoke for:

    line 25
  2. Very specific:

    If this is a new Worker (no existing wrangler.jsonc), follow references/project-bootstrap.md to get a minimal Module Workers project with:

    line 54
  3. Very specific:

    If the user already has a Worker, read their existing wrangler.jsonc before suggesting changes.

    line 65
  4. Distinctive:

    Document the choice in the PR description with the rejected alternatives and why.

    line 82
  5. Very specific:

    Unit tests: import the handler, call worker.fetch(request, env, ctx) directly with mocked env

    line 105
  6. Very specific:

    Integration tests: use SELF.fetch() from cloudflare:test (in-process) or auxiliary Workers (fresh isolate)

    line 106

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Cloudflare Workers Author

Companion rule: 401-cloudflare-workers.mdc (file-scoped to wrangler.{jsonc,toml,json} and common Worker entry-point names). The rule is the gate (non-negotiables); this skill is the workflow (how to actually build the Worker).

Voice: opinionated. Cloudflare Workers is a small, fast platform with a specific runtime model. Most production incidents come from treating it like a generic Node.js server (top-level await, mutable module state, unbounded loops, secrets in logs, public-URL Worker-to-Worker calls). The workflow below prevents those.


When to invoke this skill

Invoke when:

  • The user asks to build a new Cloudflare Worker, Durable Object, Workflow, Queue consumer, Cron Trigger, Email Worker, or Pages Function.
  • The user is adding routes / endpoints to an existing Worker.
  • The user asks "should I use KV / D1 / R2 / DO / Hyperdrive for this?" (the bindings-and-storage decision matrix is the answer).
  • The user is setting up testing for a Worker.
  • The user is configuring deployment (gradual rollouts, OIDC for GitHub Actions, secret rotation).
  • A PR review touches wrangler.jsonc, a Worker entry-point file, or a binding configuration.

Do NOT invoke for:

  • Cloudflare Pages static-site work (not Worker authoring). See cloud-platforms/references/cloudflare.md § Pages.
  • WAF rule authoring (use cloudflare-waf-author skill instead).
  • Generic JavaScript / TypeScript code that happens to be imported by a Worker - delegate to the typescript-javascript skill.

Pre-step: classify what you're building

Each Worker type has a different entry-point shape and different bindings emphasis. Ask the user which one if unclear - the wrong entry-point shape is a wrong-handler-signature bug that won't surface until deploy time.

TypeEntry pointMost common bindings
Default-export HTTP Workerexport default { async fetch(req, env, ctx) {...} } satisfies ExportedHandler<Env>KV, D1, R2, AI, Service Bindings
Named-entrypoint (RPC target)export default class extends WorkerEntrypoint { async myMethod() {...} }Same, called via Service Binding from another Worker
Durable Objectexport class Room extends DurableObject {...} + a default fetch handler that routes to DODO storage + KV / R2 for cold data
Workflowexport class MyWorkflow extends WorkflowEntrypoint { async run(event, step) {...} }KV, D1, R2, Service Bindings, AI
Queue consumerexport default { async queue(batch, env, ctx) {...} }D1, R2, Service Bindings, AI
Cron Triggerexport default { async scheduled(event, env, ctx) {...} }KV, D1, R2, Service Bindings
Email Workerexport default { async email(message, env, ctx) {...} }R2 for attachments, D1 for logs
Pages Functionexport const onRequest: PagesFunction<Env> = async (ctx) => {...} (in functions/[[path]].ts)KV, D1, R2 (subset of full Worker bindings)

The six-step workflow

Step 1 - Bootstrap the project

If this is a new Worker (no existing wrangler.jsonc), follow references/project-bootstrap.md to get a minimal Module Workers project with:

  • wrangler.jsonc (the modern config format - JSONC with comments; recommended by Cloudflare since Wrangler v3.91.0)
  • package.json with wrangler, typescript, and a types script that runs wrangler types
  • tsconfig.json with strict mode, ESNext target, "types": [], and the generated worker-configuration.d.ts in include
  • src/worker.ts with the generated Env type (do not hand-author it) and satisfies ExportedHandler<Env>
  • .dev.vars in .gitignore; worker-configuration.d.ts generated by wrangler types

[!IMPORTANT] wrangler types now generates the Workers runtime types and supersedes @cloudflare/workers-types. Current Wrangler (4.9x+) prints this on every wrangler types run and tells you to uninstall @cloudflare/workers-types and remove it from tsconfig.json. The generated worker-configuration.d.ts provides both the global Env (from vars + declared secrets) and the runtime globals (Request, Response, ExecutionContext, ExportedHandler, etc.), pinned to your compatibility_date. So: no @cloudflare/workers-types dependency, "types": [] in tsconfig.json, and worker-configuration.d.ts in include. Declare expected secrets under secrets.required in wrangler.jsonc so they appear in the generated Env. Vars are typed as their literal values by default; run wrangler types --strict-vars=false to type them as string when a var's value varies by environment.

If the user already has a Worker, read their existing wrangler.jsonc before suggesting changes. Don't propose a fresh structure that conflicts with their conventions.

Step 2 - Choose bindings and storage

This is the most consequential design decision in any new Worker. Pick wrong and you'll be migrating data in six months. The full decision matrix is in references/bindings-and-storage.md; the headline:

NeedUseDon't use
Session / config / feature flag (small, eventually consistent, low write)KVD1, R2
Relational queries, transactions, joins (single-region, SQLite scale)D1KV
File / blob storage (any size; metadata + body)R2KV (25 MB value limit)
Strongly-consistent coordination (sessions, rate limits, websocket fan-out)Durable ObjectKV (eventually consistent), D1 (single-region, not strongly consistent across Workers)
Connection pooling to existing Postgres / MySQLHyperdriveDirect TCP (works but no pooling)
Job queues, dead-letter, batchingQueuesManual KV-based queue
LLM inferenceWorkers AI behind AI Gateway (cost cap + caching + observability)Direct provider API calls
Vector searchVectorizeKV with embedding lookups (no ANN index)

Document the choice in the PR description with the rejected alternatives and why.

Step 3 - Route requests

Pick a router based on complexity:

ComplexityChoiceWhy
1-3 routes, no middlewareNative URL + switchZero deps, smallest bundle
Many routes, middleware, typed envHono (de facto)Typed, fast, designed for edge runtimes, OpenAPI integration available
Worker-to-Worker calls onlyRPC via WorkerEntrypointNo HTTP overhead, typed arguments and return values, faster than HTTP service bindings
GraphQLGraphQL Yoga + HonoYoga has a Workers adapter

For Hono patterns (typed env via Hono<{ Bindings: Env, Variables: ... }>, middleware, error boundaries, OpenAPI) and RPC patterns (when to use, named entrypoints, lifecycle), see references/hono-patterns.md.

Step 4 - Test

The canonical testing path is @cloudflare/vitest-plugin v1+ with Vitest 4.1+. It runs tests inside the Workers runtime via Miniflare, with bindings mocked or real (your choice), HMR for fast reruns, and isolated per-test storage.

See references/testing-with-vitest-pool.md for setup details. The pattern:

  • vitest.config.ts registers the cloudflareTest() Vite plugin (exported from @cloudflare/vitest-plugin) inside a standard defineConfig from vitest/config, pointing at your wrangler.jsonc
  • tsconfig.json for tests includes "@cloudflare/vitest-plugin/types" in types (declares the cloudflare:test module)
  • Unit tests: import the handler, call worker.fetch(request, env, ctx) directly with mocked env
  • Integration tests: use SELF.fetch() from cloudflare:test (in-process) or auxiliary Workers (fresh isolate)

[!IMPORTANT] Cloudflare renamed @cloudflare/vitest-pool-workers to @cloudflare/vitest-plugin in v1. Migrate the dependency, package imports, and TypeScript types entry. The configuration API remains cloudflareTest({ wrangler: { configPath: "./wrangler.jsonc" } }) inside plugins in a vitest/config defineConfig; do not restore the removed defineWorkersConfig helper.

Reject in review: new tests using unstable_dev or the renamed @cloudflare/vitest-pool-workers package.

Step 5 - Deploy and roll out gradually

Workers supports two deployment models:

  • Standard deploy (wrangler deploy) - upload and immediately serve 100% from the new version. Fine for low-risk changes.
  • Gradual deployment (wrangler versions upload then wrangler versions deploy) - upload as a new version without serving traffic, split traffic between old and new, then promote to 100%. Required for production HTTP Workers serving customer traffic.

Headline commands:

# Upload new version without deploying
wrangler versions upload

# Interactive split (prompts for percentages)
wrangler versions deploy

# Non-interactive: 90% old, 10% new
wrangler versions deploy <old-version-id>@90 <new-version-id>@10

# Promote to 100%
wrangler versions deploy <new-version-id>@100

Smoke-test a specific version in production via the Cloudflare-Workers-Version-Overrides request header (the version override applies even when its percentage is 0%).

For CI integration (OIDC trust for GitHub Actions instead of long-lived API tokens), secret rotation, and the canary-then-promote pattern, see references/deployment-and-versions.md.

Step 6 - Verify and ship

Before merge:

  • wrangler types regenerated; worker-configuration.d.ts matches wrangler.jsonc
  • tsc --noEmit clean
  • wrangler deploy --dry-run clean (catches binding misconfigurations)
  • @cloudflare/vitest-plugin tests pass
  • PR description names the rejected storage alternatives and why
  • PR description names the soak plan for the gradual rollout (e.g., "10% for 1h, then 50% for 1h, then 100%")
  • Reviewer checklist from 401-cloudflare-workers.mdc ticked

Self-check before submitting

The high-leverage subset of the 401-cloudflare-workers.mdc reviewer checklist:

  • Module Workers syntax (export default {...} or class extends WorkerEntrypoint); no addEventListener("fetch", ...)
  • Env interface imported from generated worker-configuration.d.ts; satisfies ExportedHandler<Env> on the default export
  • No secrets in console.log, error messages, response bodies, or thrown errors
  • All fire-and-forget async work wrapped in ctx.waitUntil()
  • Cloudflare resources declared as bindings (not env vars); Worker-to-Worker calls use Service Bindings + RPC (not public URLs)
  • Subrequest budget considered; loops use batching where the API supports it (D1 batch(), etc.)
  • No top-level await, no module-scope mutable state, no module-scope fetch()
  • compatibility_date set; compatibility_flags minimal and justified
  • wrangler.jsonc is source of truth; deployment from CI (not Dashboard "Edit code")
  • Tests use @cloudflare/vitest-plugin (not deprecated unstable_dev)

See references/common-pitfalls.md for the full list of failure modes observed across many Workers teams, organized by symptom (cold-start regressions, intermittent state pollution, secret leaks, subrequest budget exhaustion, deployment drift).


Related

  • 401-cloudflare-workers.mdc - the rule (file-scoped non-negotiables)
  • 400-cloudflare.mdc - broader Cloudflare platform playbook
  • 405-cloudflare-waf-rules.mdc - file-scoped WAF rule playbook
  • cloud-platforms skill - cross-cloud platform patterns
  • cloudflare-waf-author skill - WAF rule authoring workflow companion
  • skills/typescript-javascript - general TypeScript / JavaScript patterns

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.