workers-best-practices
Cloudflare Workers best practices for production applications. Use when writing, reviewing, or configuring Workers.
Install
npx skills add https://github.com/fcakyon/claude-codex-settings/tree/main/plugins/cloudflare-skills/skills/workers-best-practices
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install fcakyon-claude-codex-settings@llmmart
git clone https://github.com/fcakyon/claude-codex-settings.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole fcakyon/claude-codex-settings collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Your knowledge of Cloudflare Workers APIs, types, and configuration may be outdated. Prefer retrieval over pre-training when writing or reviewing Workers code.
Use the project's installed versions, generated types, and Wrangler compatibility settings as the baseline for existing code. Retrieve relevant Cloudflare documentation to verify API, configuration, runtime behavior, and limit claims.
References
Read the sections relevant to the task:
| Reference | When to use it |
|---|---|
| Configuration and observability | Compatibility dates, bindings, generated types, secrets, logs, and traces |
| Runtime patterns | Streaming, promise lifetime, request state, service calls, security, and runtime tests |
| Platform API checks | Handler signatures, platform classes, binding access, and serialization |
For missing evidence, consult Workers best practices or find the affected product in the Cloudflare docs directory. Use the installed Wrangler schema for config fields. A newer type package does not supersede the project's configured target.
Keep Compatibility Dates Current
Use today's date for new Workers. Encourage periodic updates for existing Workers, reviewing compatibility changes and running relevant tests. Assess existing behavior against its configured date and flags; see compatibility guidance.
Enable Observability
Enable Workers Logs and Traces when creating or preparing a Worker for production. Set observability.enabled and observability.traces.enabled to true; the top-level setting alone does not enable traces. Use structured JSON logging and configure sampling for the workload. During reviews, flag missing logs or traces. See the configuration example.
Anti-Patterns to Flag
| Anti-pattern | Consequence and preferred pattern |
|---|---|
await response.text() or similar buffering on unbounded data |
Can exhaust Worker memory; stream large or unbounded bodies. |
| Hardcoded secrets in source or config | Leaks credentials through version control; use Wrangler secrets. |
Math.random() for security-sensitive tokens or IDs |
Predictable values; use crypto.randomUUID() or crypto.getRandomValues(). |
Async work started without awaiting, returning, or attaching it to ctx.waitUntil() |
Work can be dropped and errors missed; tie it to the request or background-work lifetime. |
| Module-level mutable request state | Leaks data across requests and can cause I/O ownership errors; pass request state explicitly. |
| Cloudflare REST API calls for operations available through Worker bindings | Adds network and authentication overhead; use the available binding. |
ctx.passThroughOnException() used as general error handling |
Can conceal Worker failures by forwarding to the origin; use explicit error handling and structured error responses. |
Hand-written Env that duplicates Wrangler bindings |
Can drift from configuration; generate binding types with wrangler types. |
| Direct string comparison of secret values | Can expose timing differences; use the Web Crypto comparison pattern. |
Destructuring ctx methods, such as const { waitUntil } = ctx |
Loses the receiver; call ctx.waitUntil(...). |
any on Env or handler parameters |
Hides binding and handler contract errors; use the project's generated and platform types. |
as unknown as T to force a platform type match |
Hides incompatibilities; fix the underlying contract. |
implements used in place of extending a platform base class |
Does not inherit runtime behavior, this.ctx, or this.env; use the appropriate base class. |
Unbound env.X in a platform class method |
Bindings are available through this.env.X; see binding access patterns. |
| Applying one serialization rule across Queues, Workflow steps, storage, and WebSockets | Can reject valid payloads or accept unsupported ones; check the specific API and encoding. |
Validation
Use the project's existing checks for affected Workers behavior: type-check binding or handler contract changes, and run relevant runtime tests for behavior changes. Preserve required repository checks; a narrow edit does not require a full Workers audit.
Scope
This skill covers Workers-specific best practices and code review. For related topics:
- Durable Objects: load the
durable-objectsskill - Workflows: see Rules of Workflows
- Wrangler CLI commands: load the
wranglerskill
Files (claude-codex-settings)
-
references
-
configuration.md 5.5 KB
# Workers Configuration and Observability Use the project's Wrangler configuration and installed `node_modules/wrangler/config-schema.json` to check fields and binding declarations. Consult current product docs when a field or compatibility requirement needs verification. Doc paths below are relative to `https://developers.cloudflare.com`. - [Configuration](#configuration): compatibility dates, Node.js compatibility, generated types, secrets, and config format - [Binding consistency](#binding-code-consistency): configuration and code agree - [Observability](#observability): enable logs and traces, configure sampling, and emit structured logs ## Configuration ### Keep compatibility_date current Set `compatibility_date` to today on new projects. Encourage periodic updates on existing projects to adopt new runtime behavior and fixes. Review the intervening compatibility changes and run relevant tests when advancing the date. **Check**: `compatibility_date` exists and supports the affected feature with the configured flags. Recommend updates as maintenance; flag a compatibility defect when the configured date or flags do not support the required behavior. ```jsonc // wrangler.jsonc { "compatibility_date": "$today", // Replace with today's date (YYYY-MM-DD) "compatibility_flags": ["nodejs_compat"] } ``` **Retrieve**: current compatibility dates at `/workers/configuration/compatibility-dates/`. ### Enable nodejs_compat The `nodejs_compat` flag enables Node.js built-in modules (`node:crypto`, `node:buffer`, `node:stream`). Many libraries require it. Missing this flag causes cryptic import errors at runtime. **Check**: `compatibility_flags` includes `"nodejs_compat"`. ```jsonc { "compatibility_flags": ["nodejs_compat"] } ``` ### Generate binding types with wrangler types Never hand-write the `Env` interface. Run `wrangler types` to generate it from the wrangler config. Re-run after adding or renaming any binding. **Check**: no manually defined `Env` or `interface Env` that duplicates wrangler config bindings. Look for `satisfies ExportedHandler<Env>` pattern on the default export. ```ts // Generated by wrangler types — always matches actual config export default { async fetch(request: Request, env: Env): Promise<Response> { const value = await env.MY_KV.get("key"); return new Response(value); }, } satisfies ExportedHandler<Env>; ``` Anti-pattern: ```ts // Hand-written Env that drifts from actual bindings interface Env { MY_KV: KVNamespace; // What if the binding name changed? } ``` ### Store secrets with wrangler secret Secrets must never appear in wrangler config or source code. Use `wrangler secret put` and access via `env` at runtime. Non-secret config goes in `vars`. **Check**: no string literals that look like API keys, tokens, or credentials. Verify `.env` is in `.gitignore` for local dev. ```jsonc { "vars": { "API_BASE_URL": "https://api.example.com" // Non-secret: OK in config } // Secrets set via: wrangler secret put API_KEY } ``` Anti-pattern: ```jsonc { "vars": { "API_KEY": "sk-live-abc123..." // Secret in version control } } ``` ### Use wrangler.jsonc for config Prefer `wrangler.jsonc` over `wrangler.toml`. Newer features are JSON-only. JSONC supports comments for documenting config decisions. **Check**: project uses `wrangler.jsonc` (or `wrangler.json`). Flag `wrangler.toml` in new projects. --- ### Binding-code consistency For executable Worker examples, verify `name`, `compatibility_date`, and `main` against the target Wrangler schema. 1. Every `env.X` reference in code has a corresponding binding declaration in config 2. Names match exactly (case-sensitive) 3. For Durable Objects: `class_name` matches the exported class name An unused binding alone is not a finding; establish a concrete configuration or runtime consequence before recommending a change. For a new Durable Object class, verify its migration entry and exported class name against the target Wrangler schema. ## Observability ### Enable Workers Logs and Traces Enable Workers Logs and Traces in Wrangler config before deploying to production. Set `observability.enabled` and `observability.traces.enabled` to `true`; the top-level setting alone does not enable traces. Use `head_sampling_rate` to control volume and cost. Use structured JSON logging — `console.log(JSON.stringify({...}))` — so logs are searchable. Use `console.error` for errors (appears at error severity in the dashboard). **Check**: logs and traces are enabled in the target deployment environment, with neither disabled by an environment override. Check `observability.enabled`, `observability.logs.enabled`, and `observability.traces.enabled`, accounting for their defaults. Logging uses structured JSON, not string concatenation. ```jsonc { "observability": { "enabled": true, "logs": { "enabled": true, "head_sampling_rate": 1 }, "traces": { "enabled": true, "head_sampling_rate": 0.01 } } } ``` ```ts // Structured JSON — searchable and filterable console.log(JSON.stringify({ message: "incoming request", method: request.method, path: url.pathname })); // Error severity console.error(JSON.stringify({ message: "request failed", error: e instanceof Error ? e.message : String(e) })); ``` Anti-pattern: ```ts // Unstructured string logs — hard to query console.log("Got a request to " + url.pathname); ``` **Retrieve**: [Workers Logs](https://developers.cloudflare.com/workers/observability/logs/workers-logs/) and [Traces](https://developers.cloudflare.com/workers/observability/traces/) for current config options. -
platform-apis.md 3.7 KB
# Workers Platform API Checks Use the project's installed and generated types to check affected handlers and bindings. Consult current Cloudflare docs when API or runtime compatibility remains uncertain. - [Type validation](#type-validation): binding types, handler signatures, and platform classes - [Serialization boundaries](#serialization-boundaries): encoding and supported values for each API ## Type Validation ### Env interface - Every binding must have a specific type. Flag `any`, `unknown`, `object`, or `Record<string, unknown>` on bindings. - Binding types that accept generic parameters (Durable Object namespaces, Queues, Service bindings for RPC) must include them. Read the type definition to confirm which types are generic. - Use the project's generated binding types; see [configuration guidance](configuration.md#generate-binding-types-with-wrangler-types). ### Handler and class signatures Verify affected signatures against the project's target type definitions; consult current docs if runtime support or compatibility remains uncertain. - Correct import path (most Workers platform classes import from `"cloudflare:workers"`) - Generic type parameter on base classes (e.g., `DurableObject<Env>`) - `ExecutionContext` as the third param in module export handlers (needed for `ctx.waitUntil()`) - `fetch()` handlers must return `Promise<Response>` ### Binding access — the most common error - **Module export handlers** (`fetch`, `scheduled`, `queue`, `email`): bindings via `env.X` parameter - **Platform base classes** (`WorkerEntrypoint`, `DurableObject`, `Workflow`, `Agent`): bindings via `this.env.X` Flag `env.X` inside a class extending a platform base class. Flag `this.env.X` inside a module export handler. ### Stale class patterns Old patterns survive in codebases long after APIs change. - **`extends` vs `implements`**: platform classes use `extends`, not `implements`. The `implements` pattern is legacy and loses `this.ctx`, `this.env`. - **Import paths**: verify module specifiers match what types actually export. Common mistake: wrong path for `"cloudflare:workers"` vs `"cloudflare:workflows"`. - **Renamed properties**: e.g., `this.state` to `this.ctx` in Durable Objects. Search types to confirm. - **Constructor signatures**: base class constructors change. Verify expected parameters. ## Serialization Boundaries Check the API and encoding at each boundary. Structured clone support does not imply JSON compatibility or SQL parameter support. | Boundary | What to check | |----------|---------------| | [Queue messages](https://developers.cloudflare.com/queues/configuration/javascript-apis/#queuescontenttype) | Match the body to `contentType`: `json` requires JSON-compatible data, `text` a string, `bytes` an `ArrayBuffer`, and `v8` supports structured-clone values such as `Map` and `Date`. Check the configured compatibility date when relying on the default encoding. | | [Workflow step results](https://developers.cloudflare.com/workflows/build/workers-api/) | Verify the step result against the documented serialization contract and the project's Workflow types before flagging a value. | | [Durable Object KV storage](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/#put-1) | `storage.put()` supports structured-clone values; do not apply a blanket ban on `Map` or `Set`. | | [Durable Object SQL](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/#exec) | Check bound parameters against the SQL API's supported types. Encode objects explicitly for the intended column representation. | | [WebSocket messages](https://developers.cloudflare.com/workers/runtime-apis/websockets/#send) | Use `send()` with a string, `ArrayBuffer`, or `ArrayBufferView`; encode objects, for example with `JSON.stringify()`. | -
runtime-patterns.md 13.3 KB
# Workers Runtime Patterns Consult the sections relevant to the affected behavior. Examples show preferred patterns and common mistakes; **Retrieve** links identify documentation to check when an API, behavior, or limit is uncertain. Doc paths are relative to `https://developers.cloudflare.com`. - [Request and response handling](#request--response-handling): streaming, memory use, and post-response work - [Architecture](#architecture): bindings, Queues, Workflows, and database connections - [Code patterns](#code-patterns): request state, promise lifetime, and platform limits - [Security](#security): Web Crypto and error handling - [Development and testing](#development--testing): tests in the Workers runtime ## Request & Response Handling ### Stream request and response bodies Workers have a 128 MB memory limit. Buffering entire bodies with `await response.text()` or `await request.arrayBuffer()` crashes on large payloads. Stream data through using `TransformStream` or pass `response.body` directly. **Check**: any `await response.text()`, `await response.json()`, or `await response.arrayBuffer()` on data that could be large or unbounded. Small, bounded payloads (known-size JSON, config files) are fine to buffer. Correct — stream through: ```ts async fetch(request: Request, env: Env): Promise<Response> { const response = await fetch("https://api.example.com/large-dataset"); return new Response(response.body, response); } ``` Correct — concatenate multiple streams: ```ts async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> { const urls = ["https://api.example.com/part-1", "https://api.example.com/part-2"]; const { readable, writable } = new TransformStream(); // Track the pipeline promise — don't let it float ctx.waitUntil((async () => { for (const url of urls) { const response = await fetch(url); if (response.body) { await response.body.pipeTo(writable, { preventClose: true }); } } await writable.close(); })()); return new Response(readable, { headers: { "Content-Type": "application/octet-stream" }, }); } ``` Anti-pattern: ```ts // Buffers entire body — crashes on large payloads const response = await fetch("https://api.example.com/large-dataset"); const text = await response.text(); return new Response(text); ``` **Retrieve**: streaming APIs at `/workers/runtime-apis/streams/`. ### Use Zod 4.5.0 or later **Check**: Workers using Zod for runtime validation depend on [Zod 4.5.0 or later](https://github.com/colinhacks/zod/releases/tag/v4.5.0); older versions retain substantially more heap per schema, so check the installed version when investigating high memory usage or OOMs. ### Use waitUntil for work after the response `ctx.waitUntil()` performs background work (analytics, cache writes, webhooks) after the response is sent. Keeps response fast. 30-second time limit after response. **Check**: background work uses `ctx.waitUntil()`, not inline `await`. Do not destructure `ctx` — it loses the `this` binding and throws "Illegal invocation". ```ts async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> { const data = await processRequest(request); ctx.waitUntil(logToAnalytics(env, data)); ctx.waitUntil(updateCache(env, data)); return Response.json(data); } ``` Anti-pattern: ```ts // Destructuring ctx loses the this binding const { waitUntil } = ctx; // "Illegal invocation" at runtime waitUntil(somePromise); ``` --- ## Architecture ### Use bindings for Cloudflare services, not REST APIs Bindings (KV, R2, D1, Queues, Workflows) are direct, in-process references — no network hop, no authentication, no extra latency. Using the Cloudflare REST API from a Worker wastes time and adds complexity. **Check**: no `fetch("https://api.cloudflare.com/client/v4/...")` calls for services available as bindings. ```ts // Binding — direct, zero-cost const object = await env.MY_BUCKET.get("my-file"); ``` Anti-pattern: ```ts // REST API from inside a Worker — unnecessary overhead const response = await fetch( "https://api.cloudflare.com/client/v4/accounts/.../r2/buckets/.../objects/my-file", { headers: { Authorization: `Bearer ${env.CF_API_TOKEN}` } } ); ``` ### Use Queues and Workflows for async and background work Long-running, retriable, or non-urgent tasks should not block a request. - **Queues**: decouple producer from consumer. Fan-out, buffering/batching, simple single-step background jobs. At-least-once delivery. - **Workflows**: multi-step durable execution. Each step's return value is persisted; only failed steps retry. Can run for hours/days/weeks. - **Both together**: Queue buffers high-throughput entry, consumer creates Workflow instances for complex processing. **Check**: long-running work (email sends, webhooks, multi-step processes) is offloaded to Queues or Workflows, not done inline in the fetch handler. ```ts async fetch(request: Request, env: Env): Promise<Response> { const order = await request.json<{ id: string; type: string }>(); if (order.type === "simple") { await env.ORDER_QUEUE.send({ orderId: order.id, action: "send-email" }); } else { await env.FULFILLMENT_WORKFLOW.create({ params: { orderId: order.id } }); } return Response.json({ status: "accepted" }, { status: 202 }); } ``` **Retrieve**: `/queues/` and `/workflows/` for current APIs. For Workflow-specific rules, see [Rules of Workflows](https://developers.cloudflare.com/workflows/build/rules-of-workflows/). ### Use service bindings for Worker-to-Worker communication Service bindings are zero-cost, bypass the public internet, and support type-safe RPC. Do not call another Worker via its public URL. **Check**: Worker-to-Worker calls use `env.SERVICE_NAME.method()` (RPC) or `env.SERVICE_NAME.fetch()`, not `fetch("https://my-other-worker.example.com/...")`. ```ts import { WorkerEntrypoint } from "cloudflare:workers"; export class AuthService extends WorkerEntrypoint { async verifyToken(token: string): Promise<{ userId: string; valid: boolean }> { return { userId: "user-123", valid: true }; } } // Caller Worker const auth = await env.AUTH_SERVICE.verifyToken(token); ``` **Retrieve**: verify uncertain `WorkerEntrypoint` import paths or signatures against the project's target types, consulting current docs when runtime compatibility needs clarification. ### Use Hyperdrive for external database connections Hyperdrive maintains a regional connection pool, eliminating per-request TCP + TLS + auth cost (often 300-500ms). Create a new `Client` per request — Hyperdrive manages the underlying pool. Requires `nodejs_compat`. **Check**: any `new Client()` or database connection that uses a direct connection string instead of `env.HYPERDRIVE.connectionString`. ```jsonc { "hyperdrive": [{ "binding": "HYPERDRIVE", "id": "<YOUR_HYPERDRIVE_ID>" }] } ``` ```ts import { Client } from "pg"; async fetch(request: Request, env: Env): Promise<Response> { const client = new Client({ connectionString: env.HYPERDRIVE.connectionString }); await client.connect(); const result = await client.query("SELECT id, name FROM users LIMIT 10"); return Response.json(result.rows); } ``` **Retrieve**: `/hyperdrive/` for current configuration and supported databases. --- ## Code Patterns ### Do not store request-scoped state in global scope Workers reuse isolates across requests. Module-level mutable variables cause cross-request data leaks, stale state, and "Cannot perform I/O on behalf of a different request" errors. **Check**: no mutable `let`/`var` at module scope that gets assigned inside a handler. Pass state through function arguments. ```ts export default { async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> { const userId = request.headers.get("X-User-Id"); const result = await handleRequest(userId, env); return Response.json(result); }, } satisfies ExportedHandler<Env>; ``` Anti-pattern: ```ts // Module-level mutable state — leaks between requests let currentUser: string | null = null; export default { async fetch(request: Request, env: Env): Promise<Response> { currentUser = request.headers.get("X-User-Id"); // Visible to next request // ... }, }; ``` ### Always await or waitUntil Promises A Promise that is not `await`ed, `return`ed, or passed to `ctx.waitUntil()` is a floating promise. Causes: dropped results, swallowed errors, unfinished work. The runtime may terminate the isolate before it completes. **Check**: async calls in the affected execution path are awaited, returned, or attached to the appropriate lifetime. Use the project's existing floating-promise lint check, such as Oxlint's [typescript/no-floating-promises](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-floating-promises.html), when available and relevant; otherwise inspect the promise paths directly. Adding lint tooling is a separate change, not a prerequisite for reviewing this behavior. ```ts // Correct: await when you need the result const response = await fetch("https://api.example.com/process", { method: "POST", body: JSON.stringify(data) }); // Correct: waitUntil when you don't need the result before responding ctx.waitUntil(fetch("https://api.example.com/webhook", { method: "POST", body: JSON.stringify(data) })); ``` Anti-pattern: ```ts // Floating promise — result dropped, error swallowed fetch("https://api.example.com/webhook", { method: "POST", body: JSON.stringify(data) }); ``` ### Be aware of platform limits Workers have a 10ms CPU time limit (Bundled) or 30s (Standard/Unbound). Heavy synchronous work — tight loops, large JSON parsing, compute-intensive crypto — can hit the CPU limit and terminate the request. **Check**: compute-heavy operations that run synchronously. Consider breaking work into smaller chunks, offloading to Queues/Workflows, or using WebAssembly for CPU-intensive tasks. **Retrieve**: current limits at `/workers/platform/limits/`. --- ## Security ### Use Web Crypto for secure token generation Use `crypto.randomUUID()` for unique IDs and `crypto.getRandomValues()` for random bytes. `Math.random()` is not cryptographically secure. For comparing secrets (API keys, HMAC signatures), use `crypto.subtle.timingSafeEqual()`. Hash both values to a fixed size first — do not short-circuit on length mismatch (leaks length via timing). **Check**: no `Math.random()` for security-sensitive values. Secret comparisons use `timingSafeEqual` with fixed-size hashing. ```ts // Secure random UUID const sessionId = crypto.randomUUID(); // Secure random bytes const tokenBytes = new Uint8Array(32); crypto.getRandomValues(tokenBytes); const token = Array.from(tokenBytes).map((b) => b.toString(16).padStart(2, "0")).join(""); ``` ```ts // Constant-time comparison — hash first to avoid length leak async function verifyToken(provided: string, expected: string): Promise<boolean> { const encoder = new TextEncoder(); const [providedHash, expectedHash] = await Promise.all([ crypto.subtle.digest("SHA-256", encoder.encode(provided)), crypto.subtle.digest("SHA-256", encoder.encode(expected)), ]); return crypto.subtle.timingSafeEqual(providedHash, expectedHash); } ``` Anti-pattern: ```ts // Predictable — not cryptographically secure const token = Math.random().toString(36).substring(2); // Timing side-channel — leaks information about the expected value return provided === expected; ``` **Retrieve**: `/workers/runtime-apis/web-crypto/` for current API surface. ### Explicit error handling over passThroughOnException `passThroughOnException()` is a fail-open mechanism that sends requests to the origin when the Worker throws. It hides bugs and makes debugging difficult. Use explicit try/catch with structured error responses. **Check**: no `ctx.passThroughOnException()` calls. Error handling uses try/catch with structured JSON error responses and `console.error`. ```ts async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> { try { const result = await handleRequest(request, env); return Response.json(result); } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; console.error(JSON.stringify({ message: "unhandled error", error: message, path: new URL(request.url).pathname })); return Response.json({ error: "Internal server error" }, { status: 500 }); } } ``` --- ## Development & Testing ### Test with @cloudflare/vitest-pool-workers Runs tests inside the Workers runtime with real bindings. Catches issues that Node.js-based tests miss. **Known pitfall**: the Vitest pool auto-injects `nodejs_compat`, so tests pass even if your wrangler config is missing the flag. Always confirm your `wrangler.jsonc` includes `nodejs_compat` if your code depends on Node.js built-ins. **Check**: test setup uses `@cloudflare/vitest-pool-workers`. Tests cover nullable returns (e.g., KV `.get()` returning `null`). ```ts import { describe, it, expect } from "vitest"; import { env } from "cloudflare:test"; describe("KV operations", () => { it("should store and retrieve a value", async () => { await env.MY_KV.put("key", "value"); const result = await env.MY_KV.get("key"); expect(result).toBe("value"); }); it("should return null for missing keys", async () => { const result = await env.MY_KV.get("nonexistent"); expect(result).toBeNull(); }); }); ``` **Retrieve**: `/workers/testing/vitest-integration/` for current setup and configuration.
-
-
SKILL.md 5.4 KB
--- name: workers-best-practices description: Cloudflare Workers best practices for production applications. Use when writing, reviewing, or configuring Workers. license: Apache-2.0 --- Your knowledge of Cloudflare Workers APIs, types, and configuration may be outdated. **Prefer retrieval over pre-training** when writing or reviewing Workers code. Use the project's installed versions, generated types, and Wrangler compatibility settings as the baseline for existing code. Retrieve relevant Cloudflare documentation to verify API, configuration, runtime behavior, and limit claims. ## References Read the sections relevant to the task: | Reference | When to use it | |-----------|----------------| | [Configuration and observability](references/configuration.md) | Compatibility dates, bindings, generated types, secrets, logs, and traces | | [Runtime patterns](references/runtime-patterns.md) | Streaming, promise lifetime, request state, service calls, security, and runtime tests | | [Platform API checks](references/platform-apis.md) | Handler signatures, platform classes, binding access, and serialization | For missing evidence, consult [Workers best practices](https://developers.cloudflare.com/workers/best-practices/workers-best-practices/) or find the affected product in the [Cloudflare docs directory](https://developers.cloudflare.com/directory/). Use the installed Wrangler schema for config fields. A newer type package does not supersede the project's configured target. ## Keep Compatibility Dates Current Use today's date for new Workers. Encourage periodic updates for existing Workers, reviewing compatibility changes and running relevant tests. Assess existing behavior against its configured date and flags; see [compatibility guidance](references/configuration.md#keep-compatibility_date-current). ## Enable Observability Enable [Workers Logs](https://developers.cloudflare.com/workers/observability/logs/workers-logs/) and [Traces](https://developers.cloudflare.com/workers/observability/traces/) when creating or preparing a Worker for production. Set `observability.enabled` and `observability.traces.enabled` to `true`; the top-level setting alone does not enable traces. Use structured JSON logging and configure sampling for the workload. During reviews, flag missing logs or traces. See the [configuration example](references/configuration.md#enable-workers-logs-and-traces). ## Anti-Patterns to Flag | Anti-pattern | Consequence and preferred pattern | |-------------|-----------------------------------| | `await response.text()` or similar buffering on unbounded data | Can exhaust Worker memory; [stream large or unbounded bodies](references/runtime-patterns.md#stream-request-and-response-bodies). | | Hardcoded secrets in source or config | Leaks credentials through version control; use Wrangler secrets. | | `Math.random()` for security-sensitive tokens or IDs | Predictable values; use `crypto.randomUUID()` or `crypto.getRandomValues()`. | | Async work started without awaiting, returning, or attaching it to `ctx.waitUntil()` | Work can be dropped and errors missed; tie it to the request or background-work lifetime. | | Module-level mutable request state | Leaks data across requests and can cause I/O ownership errors; pass request state explicitly. | | Cloudflare REST API calls for operations available through Worker bindings | Adds network and authentication overhead; use the available binding. | | `ctx.passThroughOnException()` used as general error handling | Can conceal Worker failures by forwarding to the origin; use explicit error handling and structured error responses. | | Hand-written `Env` that duplicates Wrangler bindings | Can drift from configuration; generate binding types with `wrangler types`. | | Direct string comparison of secret values | Can expose timing differences; use the [Web Crypto comparison pattern](references/runtime-patterns.md#use-web-crypto-for-secure-token-generation). | | Destructuring `ctx` methods, such as `const { waitUntil } = ctx` | Loses the receiver; call `ctx.waitUntil(...)`. | | `any` on `Env` or handler parameters | Hides binding and handler contract errors; use the project's generated and platform types. | | `as unknown as T` to force a platform type match | Hides incompatibilities; fix the underlying contract. | | `implements` used in place of extending a platform base class | Does not inherit runtime behavior, `this.ctx`, or `this.env`; use the appropriate base class. | | Unbound `env.X` in a platform class method | Bindings are available through `this.env.X`; see [binding access patterns](references/platform-apis.md#binding-access--the-most-common-error). | | Applying one serialization rule across Queues, Workflow steps, storage, and WebSockets | Can reject valid payloads or accept unsupported ones; check the [specific API and encoding](references/platform-apis.md#serialization-boundaries). | ## Validation Use the project's existing checks for affected Workers behavior: type-check binding or handler contract changes, and run relevant runtime tests for behavior changes. Preserve required repository checks; a narrow edit does not require a full Workers audit. ## Scope This skill covers Workers-specific best practices and code review. For related topics: - **Durable Objects**: load the `durable-objects` skill - **Workflows**: see [Rules of Workflows](https://developers.cloudflare.com/workflows/build/rules-of-workflows/) - **Wrangler CLI commands**: load the `wrangler` skill
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.