Preface¶
When writing Cloudflare Workers, the code superficially looks like regular TypeScript: a fetch handler, a few awaits, then return new Response(...). After going live, problems often stem from runtime conventions rather than syntax. Using await response.text() on an unknown-sized response body can max out the Worker’s memory; caching the current user with let at the module top level will persist across requests; writing a bare fetch() without await may cause the isolate to be recycled before the Promise resolves. These patterns are often just “not very elegant” in Node.js services, but in Workers they can lead to data leaks, swallowed errors, or outright crashes.
On the other hand, PR reviews in teams often turn into repetitive checklists: Is the compatibility_date too old? Have secrets been hardcoded into vars? Was Env handwritten or generated by wrangler types? Is observability enabled? AI coding assistants that rely solely on training data are even more likely to transplant Node.js habits to the edge, providing outdated binding types or deprecated configuration fields.
Cloudflare officially maintains a dedicated Skill in the cloudflare/skills repository, named workers-best-practices. Instead of rehashing Workers basics, it turns the workflow of “first check the current documentation, then write/review code against a checklist” into an executable Agent workflow, covering streaming processing, floating promises, global state, secrets, bindings, and wrangler configuration.
What It Is¶
workers-best-practices is an official Cloudflare Agent Skill, available at:
https://github.com/cloudflare/skills/tree/main/skills/workers-best-practices
The positioning at the top of SKILL.md is straightforward: review and write Cloudflare Workers code according to production best practices. Load it when writing new Workers, reviewing existing code, configuring wrangler.jsonc, or checking for common anti-patterns (streaming, floating promises, global state, secrets, bindings, observability). It also explicitly states: Prefer retrieving from Cloudflare documentation instead of relying on the model’s pre-trained knowledge.
It solves two specific problems:
1. Workers have the same surface-level pitfalls as Node.js, but different runtime behaviors. If an Agent writes code based on general backend experience, it will miss constraints like isolate reuse, memory limits, and ctx binding.
2. API signatures, compatibility dates, and wrangler fields change. The Skill requires pulling the current best practices page, @cloudflare/workers-types, and the local config-schema.json before reviewing or generating code.
The same repository also has the general index Skill cloudflare, the stateful coordination-focused durable-objects, and the CLI and resource management-focused wrangler. When the task narrows down to “writing/reviewing Workers or checking wrangler configuration against current conventions”, you should use the workers-best-practices Skill.
The Skill directory has an entry point and two on-demand references:
skills/workers-best-practices/
├── SKILL.md
└── references/
├── rules.md # Rules, correct practices, and anti-patterns
└── review.md # Type checking, configuration validation, binding access, review workflow
The main text requires retrieving up-to-date resources before taking action, rather than treating the reference files as the final API manual:
| Source | How to retrieve | What it’s used for |
|---|---|---|
| Workers Best Practices | Fetch https://developers.cloudflare.com/workers/best-practices/workers-best-practices/ |
Rules, patterns, anti-patterns |
| Workers Types | See references/review.md |
API signatures, handlers, binding types |
| Wrangler Schema | node_modules/wrangler/config-schema.json |
Configuration fields, binding shapes, allowed values |
| Cloudflare Docs | Search or visit https://developers.cloudflare.com/workers/ |
APIs, compatibility dates and flags |
If the type package in your project’s node_modules is outdated, the Skill requires prioritizing the latest released version. The command to fetch the types is written in the entry file:
mkdir -p /tmp/workers-types-latest && \
npm pack @cloudflare/workers-types --pack-destination /tmp/workers-types-latest && \
tar -xzf /tmp/workers-types-latest/cloudflare-workers-types-*.tgz -C /tmp/workers-types-latest
# Types at /tmp/workers-types-latest/package/index.d.ts
Core Capabilities¶
The Skill organizes rules into categories of configuration, request/response, architecture, observability, code patterns, and security, with details in references/rules.md. Below is an explanation of what the Agent should check after loading, following the official checklist.
1. Configuration: Dates, Compatibility, Types, and Secrets¶
New projects should set compatibility_date to the current date, and existing projects should update it regularly. Enable nodejs_compat — many libraries depend on node:crypto, node:buffer, and node:stream, and without this flag, import errors at runtime will be difficult to debug. Do not handwrite the interface Env for binding types; use wrangler types to generate it from your configuration, and run the command again after adding bindings or renaming them. Store secrets via wrangler secret put instead of hardcoding them into configuration or source code; non-secret configuration should go into vars. Prefer wrangler.jsonc for new projects — the Skill notes that newer features are JSON-only, and JSONC allows adding comments to configuration decisions.
The minimal configuration looks similar to this:
{
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2026-08-16",
"compatibility_flags": ["nodejs_compat"],
"vars": {
"API_BASE_URL": "https://api.example.com"
}
// Secrets set via: wrangler secret put API_KEY
}
Replace compatibility_date with the current date when running. The example dates on the official best practices page will be updated alongside the documentation, do not copy them as fixed values.
Generate types:
npx wrangler types
Add a secret:
npx wrangler secret put API_KEY
On the code side, the Skill recommends using the generated Env and validating the export with satisfies ExportedHandler<Env> instead of maintaining your own binding interface:
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>;
2. Request and Response: Streaming and Post-Response Work¶
Workers have a 128 MB memory limit. Calling await response.text(), await response.json(), or await response.arrayBuffer() on potentially large, unknown-length data will read the entire body into memory. Buffered JSON with a known, bounded size is acceptable; for large files or upstream large datasets, pass response.body directly downstream or pipe it with TransformStream. The correct practice from the official documentation and Skill is as follows:
async fetch(request: Request, env: Env): Promise<Response> {
const response = await fetch("https://api.example.com/large-dataset");
return new Response(response.body, response);
}
When you can already return a response but have cleanup work like analytics, cache writes, or webhook calls, use ctx.waitUntil() instead of awaiting them before returning. The Skill and official documentation both emphasize: Do not destructure ctx. const { waitUntil } = ctx will lose the this context and throw an Illegal invocation error at runtime. waitUntil has approximately a 30-second window after the response is sent, refer to the current documentation for exact details.
3. Architecture: Prefer Bindings, Move Background Work Off the Hot Path¶
Use in-process bindings for KV, R2, D1, Queues, and Workflows instead of calling https://api.cloudflare.com/client/v4/... directly from the Worker. Use service bindings (RPC or env.SERVICE.fetch()) between Workers instead of public network URLs. For external PostgreSQL/MySQL, use Hyperdrive, and create a new Client() per request — connection pooling is managed by Hyperdrive; this depends on nodejs_compat.
Long-running, retryable tasks, and tasks that do not block the response should be moved off the fetch hot path to Queues or Workflows:
- Queues: Decouple producers and consumers, support fan-out, buffering, and one-time background tasks with at-least-once delivery.
- Workflows: Multi-step persistent execution, with each step’s return value persisted, only failed steps are retried, and can run for extended periods.
The specialized rules for Workflows are not included in this Skill, refer to Rules of Workflows.
4. Observability and Code Patterns¶
Before going live, enable observability in your wrangler configuration and use head_sampling_rate to control the volume of logs and traces. Use structured JSON for logs — only console.error will be logged at the error level in the console.
{
"observability": {
"enabled": true,
"logs": { "head_sampling_rate": 1 },
"traces": { "enabled": true, "head_sampling_rate": 0.01 }
}
}
console.log(JSON.stringify({
message: "incoming request",
method: request.method,
path: url.pathname,
}));
There are two code patterns that appear in nearly every review:
1. Do not put request state into module globals. Isolates are reused across requests, so a module-level let currentUser will cause data leaks, stale state, and Cannot perform I/O on behalf of a different request.
2. Every Promise must have an owner. If you need the result, await or return it; if it does not block the response, pass it to ctx.waitUntil(); or explicitly mark it with void. A bare fetch() is a floating promise: its result is discarded, errors are swallowed, and the isolate may terminate early. The Skill requires scanning with @typescript-eslint/no-floating-promises or a similar rule from oxlint.
5. Security and an Anti-Pattern List¶
Security-specific checks are concrete: use crypto.randomUUID() / crypto.getRandomValues() for tokens and IDs instead of Math.random(); compare secrets with crypto.subtle.timingSafeEqual(), first hashing to a fixed length to avoid short-circuiting based on string length. Use explicit try/catch blocks and return structured errors on failure — do not treat ctx.passThroughOnException() as error handling, as it will forward the request to the origin when the Worker throws an error, hiding bugs.
The anti-pattern list in SKILL.md is the actual checklist you will work through during reviews, aligned with the official best practices page:
| Anti-Pattern | Why It Matters |
|---|---|
Calling await response.text() on unbounded data |
Fills memory, hits the 128 MB limit |
| Hardcoding secrets in source code or configuration | Leaked to version control |
Using Math.random() for tokens/IDs |
Predictable, not cryptographically secure |
Bare fetch() without await or waitUntil |
Floating promise |
| Module-level mutable variables storing request state | Cross-request data leaks |
| Calling Cloudflare REST APIs inside a Worker | Unnecessary network hops, authentication overhead, and latency |
Using ctx.passThroughOnException() as error handling |
Hides bugs |
Handwriting Env |
Drifts from real bindings |
Comparing secrets with === |
Timing side-channel vulnerability |
Destructuring ctx |
Illegal invocation error |
Typing Env or handler parameters as any |
Loses type safety for binding access |
Using as unknown as T |
Hides type incompatibilities |
Using implements instead of extends for platform base classes |
Loses this.ctx / this.env |
Accessing env.X inside platform base classes |
Should use this.env.X inside classes |
references/review.md also adds serialization boundaries: Queue messages, Workflow step return values, Durable Object storage, and postMessage() must support structured cloning. Response, Error, class instances with methods, and Map/Set may compile but fail at runtime.
Installation and Enablement¶
The official README states that this Skill is intended for assistants that support the Agent Skills standard, including Claude Code, Cursor, OpenCode, OpenAI Codex, and Pi. Install methods vary by tool, do not mix approaches.
It is important to note: The Skills table in the repository’s README currently lists cloudflare, durable-objects, wrangler, etc., does not list workers-best-practices separately. The Skill exists in the skills/ directory on GitHub, and its YAML manifest will be automatically loaded when you review Workers, write wrangler.jsonc, or check for anti-patterns. Installing the full collection will include it; to install only this Skill, use the command with the --skill flag below.
1. Using npx skills (cross-tool universal)
Install the entire Cloudflare Skills collection:
npx skills add https://github.com/cloudflare/skills
To install only this Skill, the command provided on officialskills.sh and skills.sh is:
npx skills add https://github.com/cloudflare/skills --skill workers-best-practices
2. Claude Code (Plugin Marketplace)
/plugin marketplace add cloudflare/skills
/plugin install cloudflare@cloudflare
3. Cursor
The repository README instructs: Install from the Cursor Marketplace, or fill cloudflare/skills in Settings > Rules > Add Rule > Remote Rule (Github). Cloudflare’s Cursor Setup Documentation also provides the slash command /add-plugin cloudflare, which installs the full Cloudflare Skills collection and registers the MCP.
4. Clone and copy by directory
| Tool | Skill Directory |
|------|------------|
| Claude Code | ~/.claude/skills/ |
| Cursor | ~/.cursor/skills/ |
| OpenCode | ~/.config/opencode/skills/ |
| OpenAI Codex | ~/.codex/skills/ |
| Pi | ~/.pi/agent/skills/ |
You should copy the entire skills/workers-best-practices/ folder, preserving the relative path between SKILL.md and the references/ directory. There is also a matching SKILL.md in the OpenAI openai/plugins repository, with content consistent with the official Cloudflare repository, so Codex users may retrieve it from there.
Agents generally enable the Skill automatically. When prompts include “review this Worker”, “check for floating promises”, “help me review wrangler.jsonc”, or “does this code put request state in the global scope”, this Skill will be activated.
Typical Usage¶
The examples below are taken from the official SKILL.md and the two reference files, to illustrate how the Agent should work after loading, rather than creating a separate Workers tutorial.
1. First Retrieve, Then Review an Entire File Against the Checklist¶
The review process provided by the Skill is fixed, do not change the order:
1. Retrieve: Pull the latest best practices page, workers types, and wrangler schema
2. Read full files: Do not only look at diffs — binding access patterns require viewing the full file
3. Check types: Binding access, handler signatures, prohibit any and unsafe type assertions
4. Check config: compatibility_date, nodejs_compat, observability, secrets, whether bindings and code use matching names
5. Check patterns: Streaming, floating promises, global state, serialization boundaries
6. Check security: Web Crypto, secrets, timing-safe comparisons, error handling
7. Validate with tools: npx tsc --noEmit, and no-floating-promises linting
8. Reference rules: Cross-reference each feedback item against the correct practices in references/rules.md
You can directly write the trigger condition into your prompt:
Please review this Worker using the workers-best-practices Skill.
First fetch the current Cloudflare Workers best practices page, @cloudflare/workers-types, and wrangler's config-schema.json, do not rely solely on training data.
Focus on: whether unbounded bodies are fully read with text()/arrayBuffer(), whether there are floating promises, whether request state is stored in module globals, whether secrets are in source code, whether Env was generated by wrangler types, whether ctx was destructured.
Read the full file, do not only look at diffs. Provide the filename, line number, and supporting evidence.
The scenarios suitable for this prompt align with the Skill’s own description: reviewing Worker PRs, refactoring code that caches data in module globals, verifying bindings and observability in wrangler.jsonc, and implementing current conventions when writing a new Worker.
2. Output Format for Review Feedback¶
references/review.md requires feedback to include evidence, avoid empty statements like “suggest improvement”:
**[SEVERITY]** Brief description
`file.ts:42` — explanation with evidence
Suggested fix: `code`
Severity levels are CRITICAL (security, data loss, crash),