cloudflare-workers-author · git:20260906.8d25679 · 2026-09-06 · sha256 956a0398068bca9e
cloudflare-workers-author git:20260906.8d25679A
Immutable. This exact content is served forever at /api/v1/blob/956a0398068bca9e.
---
name: cloudflare-workers-author
description: 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.
---
# 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.
| Type | Entry point | Most common bindings |
|---|---|---|
| **Default-export HTTP Worker** | `export 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 Object** | `export class Room extends DurableObject {...}` + a default fetch handler that routes to DO | DO storage + KV / R2 for cold data |
| **Workflow** | `export class MyWorkflow extends WorkflowEntrypoint { async run(event, step) {...} }` | KV, D1, R2, Service Bindings, AI |
| **Queue consumer** | `export default { async queue(batch, env, ctx) {...} }` | D1, R2, Service Bindings, AI |
| **Cron Trigger** | `export default { async scheduled(event, env, ctx) {...} }` | KV, D1, R2, Service Bindings |
| **Email Worker** | `export default { async email(message, env, ctx) {...} }` | R2 for attachments, D1 for logs |
| **Pages Function** | `export 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](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](references/bindings-and-storage.md); the headline:
| Need | Use | Don't use |
|---|---|---|
| Session / config / feature flag (small, eventually consistent, low write) | **KV** | D1, R2 |
| Relational queries, transactions, joins (single-region, SQLite scale) | **D1** | KV |
| File / blob storage (any size; metadata + body) | **R2** | KV (25 MB value limit) |
| Strongly-consistent coordination (sessions, rate limits, websocket fan-out) | **Durable Object** | KV (eventually consistent), D1 (single-region, not strongly consistent across Workers) |
| Connection pooling to existing Postgres / MySQL | **Hyperdrive** | Direct TCP (works but no pooling) |
| Job queues, dead-letter, batching | **Queues** | Manual KV-based queue |
| LLM inference | **Workers AI** behind **AI Gateway** (cost cap + caching + observability) | Direct provider API calls |
| Vector search | **Vectorize** | KV 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:
| Complexity | Choice | Why |
|---|---|---|
| 1-3 routes, no middleware | **Native `URL` + `switch`** | Zero deps, smallest bundle |
| Many routes, middleware, typed env | **Hono** (de facto) | Typed, fast, designed for edge runtimes, OpenAPI integration available |
| Worker-to-Worker calls only | **RPC via `WorkerEntrypoint`** | No HTTP overhead, typed arguments and return values, faster than HTTP service bindings |
| GraphQL | **GraphQL Yoga** + Hono | Yoga 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](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](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:
```bash
# 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](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](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