Cloudflare Durable Objects Developer
Durable objects

Build, debug, or review Cloudflare Durable Objects code for persistent state and coordination.
--- name: durable-objects description: Build, debug, or review Cloudflare Durable Objects code for persistent state and coordination. --- # Durable Objects Build stateful, coordinated applications on Cloudflare's edge using Durable Objects. ## Retrieval Sources Your knowledge of Durable Objects APIs and configuration may be outdated. **Prefer retrieval over pre-training** for any Durable Objects task. | Resource | URL | |----------|-----| | Docs | https://developers.cloudflare.com/durable-objects/index.md | | API Reference | https://developers.cloudflare.com/durable-objects/api/index.md | | Best Practices | https://developers.cloudflare.com/durable-objects/best-practices/index.md |
Overview
Build stateful, coordinated applications on Cloudflare's edge using Durable Objects.
Manual install (npx, ZIP)
npx skills add cloudflare/skills --skill durable-objects -g -a claude-codenpx degit cloudflare/skills/skills/durable-objects#main ~/.claude/skills/durable-objectsConnects with Agent Alley MCP · agentalley.io/mcp
Files included
SKILL.md5.9 KB
Read SKILL.md here
Show the full text181 lines
Durable Objects
Build stateful, coordinated applications on Cloudflare's edge using Durable Objects.
Retrieval Sources
Your knowledge of Durable Objects APIs and configuration may be outdated. Prefer retrieval over pre-training for any Durable Objects task.
Fetch the relevant doc page when implementing features.
When to Use
- Creating new Durable Object classes for stateful coordination
- Implementing RPC methods, alarms, or WebSocket handlers
- Reviewing existing DO code for best practices
- Configuring wrangler.jsonc/toml for DO bindings and migrations
- Writing tests with Cloudflare’s Vitest integration
- Designing sharding strategies and parent-child relationships
Reference Documentation
./references/rules.md- Core rules, storage, concurrency, RPC, alarms- Testing reference - Current Vitest documentation, migration choices, and test selection
./references/workers.md- Workers handlers, types, wrangler config, observability
Search: blockConcurrencyWhile, idFromName, getByName, setAlarm, sql.exec
Core Principles
Use Durable Objects For
| Need | Example |
|---|---|
| Coordination | Chat rooms, multiplayer games, collaborative docs |
| Strong consistency | Inventory, booking systems, turn-based games |
| Per-entity storage | Multi-tenant SaaS, per-user data |
| Persistent connections | WebSockets, real-time notifications |
| Scheduled work per entity | Subscription renewals, game timeouts |
Do NOT Use For
- Stateless request handling (use plain Workers)
- Maximum global distribution needs
- High fan-out independent requests
Quick Reference
Wrangler Configuration
// wrangler.jsonc
{
"durable_objects": {
"bindings": [{ "name": "MY_DO", "class_name": "MyDurableObject" }]
},
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyDurableObject"] }]
}
Basic Durable Object Pattern
import { DurableObject } from "cloudflare:workers";
export interface Env {
MY_DO: DurableObjectNamespace<MyDurableObject>;
}
export class MyDurableObject extends DurableObject<Env> {
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
ctx.blockConcurrencyWhile(async () => {
this.ctx.storage.sql.exec(`
CREATE TABLE IF NOT EXISTS items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
data TEXT NOT NULL
)
`);
});
}
async addItem(data: string): Promise<number> {
const result = this.ctx.storage.sql.exec<{ id: number }>(
"INSERT INTO items (data) VALUES (?) RETURNING id",
data
);
return result.one().id;
}
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const stub = env.MY_DO.getByName("my-instance");
const id = await stub.addItem("hello");
return Response.json({ id });
},
};
Critical Rules
- Model around coordination atoms - One DO per chat room/game/user, not one global DO
- Use
getByName()for deterministic routing - Same input = same DO instance - Use SQLite storage - Configure
new_sqlite_classesin migrations - Initialize in constructor - Use
blockConcurrencyWhile()for schema setup only - Use RPC methods - Not fetch() handler (compatibility date >= 2024-04-03)
- Persist first, cache second - Always write to storage before updating in-memory state
- One alarm per DO -
setAlarm()replaces any existing alarm
Authorization
Durable Objects do not have separate roles or permissions; access follows the Worker that implements them. Retrieve the current Durable Objects authorization guidance before granting observability or Data Studio access, and scope the Workers role to the intended Worker or Workers product.
Anti-Patterns (NEVER)
- Single global DO handling all requests (bottleneck)
- Using
blockConcurrencyWhile()on every request (kills throughput) - Storing critical state only in memory (lost on eviction/crash)
- Using
awaitbetween related storage writes (breaks atomicity) - Holding
blockConcurrencyWhile()acrossfetch()or external I/O
Stub Creation
// Deterministic - preferred for most cases
const stub = env.MY_DO.getByName("room-123");
// From existing ID string
const id = env.MY_DO.idFromString(storedIdString);
const stub = env.MY_DO.get(id);
// New unique ID - store mapping externally
const id = env.MY_DO.newUniqueId();
const stub = env.MY_DO.get(id);
Storage Operations
// SQL (synchronous, recommended)
this.ctx.storage.sql.exec("INSERT INTO t (c) VALUES (?)", value);
const rows = this.ctx.storage.sql.exec<Row>("SELECT * FROM t").toArray();
// KV (async)
await this.ctx.storage.put("key", value);
const val = await this.ctx.storage.get<Type>("key");
Alarms
// Schedule (replaces existing)
await this.ctx.storage.setAlarm(Date.now() + 60_000);
// Handler
async alarm(): Promise<void> {
// Process scheduled work
// Optionally reschedule: await this.ctx.storage.setAlarm(...)
}
// Cancel
await this.ctx.storage.deleteAlarm();
Testing
Read the testing reference before configuring a suite or writing Durable Object tests. It routes to current setup, APIs, and examples and identifies the behavior to cover.
| 1 | |
| 2 | name durable-objects |
| 3 | description Build, debug, or review Cloudflare Durable Objects code for persistent state and coordination. |
| 4 | |
| 5 | |
| 6 | # Durable Objects |
| 7 | |
| 8 | Build stateful, coordinated applications on Cloudflare's edge using Durable Objects. |
| 9 | |
| 10 | ## Retrieval Sources |
| 11 | |
| 12 | Your knowledge of Durable Objects APIs and configuration may be outdated. **Prefer retrieval over pre-training** for any Durable Objects task. |
| 13 | |
| 14 | | Resource | URL | |
| 15 | |----------|-----| |
| 16 | | Docs | https://developers.cloudflare.com/durable-objects/index.md | |
| 17 | | API Reference | https://developers.cloudflare.com/durable-objects/api/index.md | |
| 18 | | Best Practices | https://developers.cloudflare.com/durable-objects/best-practices/index.md | |
| 19 | | Examples | https://developers.cloudflare.com/durable-objects/examples/index.md | |
| 20 | | Roles and permissions | https://developers.cloudflare.com/workers/authorization/durable-objects/index.md | |
| 21 | |
| 22 | Fetch the relevant doc page when implementing features. |
| 23 | |
| 24 | ## When to Use |
| 25 | |
| 26 | Creating new Durable Object classes for stateful coordination |
| 27 | Implementing RPC methods, alarms, or WebSocket handlers |
| 28 | Reviewing existing DO code for best practices |
| 29 | Configuring wrangler.jsonc/toml for DO bindings and migrations |
| 30 | Writing tests with Cloudflare’s Vitest integration |
| 31 | Designing sharding strategies and parent-child relationships |
| 32 | |
| 33 | ## Reference Documentation |
| 34 | |
| 35 | `./references/rules.md` - Core rules, storage, concurrency, RPC, alarms |
| 36 | [Testing reference] - Current Vitest documentation, migration choices, and test selection |
| 37 | `./references/workers.md` - Workers handlers, types, wrangler config, observability |
| 38 | |
| 39 | Search: `blockConcurrencyWhile`, `idFromName`, `getByName`, `setAlarm`, `sql.exec` |
| 40 | |
| 41 | ## Core Principles |
| 42 | |
| 43 | ### Use Durable Objects For |
| 44 | |
| 45 | | Need | Example | |
| 46 | |------|---------| |
| 47 | | Coordination | Chat rooms, multiplayer games, collaborative docs | |
| 48 | | Strong consistency | Inventory, booking systems, turn-based games | |
| 49 | | Per-entity storage | Multi-tenant SaaS, per-user data | |
| 50 | | Persistent connections | WebSockets, real-time notifications | |
| 51 | | Scheduled work per entity | Subscription renewals, game timeouts | |
| 52 | |
| 53 | ### Do NOT Use For |
| 54 | |
| 55 | Stateless request handling (use plain Workers) |
| 56 | Maximum global distribution needs |
| 57 | High fan-out independent requests |
| 58 | |
| 59 | ## Quick Reference |
| 60 | |
| 61 | ### Wrangler Configuration |
| 62 | |
| 63 | |
| 64 | // wrangler.jsonc |
| 65 | { |
| 66 | "durable_objects": { |
| 67 | "bindings": [{ "name": "MY_DO", "class_name": "MyDurableObject" }] |
| 68 | }, |
| 69 | "migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyDurableObject"] }] |
| 70 | } |
| 71 | |
| 72 | |
| 73 | ### Basic Durable Object Pattern |
| 74 | |
| 75 | |
| 76 | import { DurableObject } from "cloudflare:workers"; |
| 77 | |
| 78 | export interface Env { |
| 79 | MY_DO: DurableObjectNamespace<MyDurableObject>; |
| 80 | } |
| 81 | |
| 82 | export class MyDurableObject extends DurableObject<Env> { |
| 83 | constructor(ctx: DurableObjectState, env: Env) { |
| 84 | super(ctx, env); |
| 85 | ctx.blockConcurrencyWhile(async () => { |
| 86 | this.ctx.storage.sql.exec(` |
| 87 | CREATE TABLE IF NOT EXISTS items ( |
| 88 | id INTEGER PRIMARY KEY AUTOINCREMENT, |
| 89 | data TEXT NOT NULL |
| 90 | ) |
| 91 | `); |
| 92 | }); |
| 93 | } |
| 94 | |
| 95 | async addItem(data: string): Promise<number> { |
| 96 | const result = this.ctx.storage.sql.exec<{ id: number }>( |
| 97 | "INSERT INTO items (data) VALUES (?) RETURNING id", |
| 98 | data |
| 99 | ); |
| 100 | return result.one().id; |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | export default { |
| 105 | async fetch(request: Request, env: Env): Promise<Response> { |
| 106 | const stub = env.MY_DO.getByName("my-instance"); |
| 107 | const id = await stub.addItem("hello"); |
| 108 | return Response.json({ id }); |
| 109 | }, |
| 110 | }; |
| 111 | |
| 112 | |
| 113 | ## Critical Rules |
| 114 | |
| 115 | **Model around coordination atoms** - One DO per chat room/game/user, not one global DO |
| 116 | **Use `getByName()` for deterministic routing** - Same input = same DO instance |
| 117 | **Use SQLite storage** - Configure `new_sqlite_classes` in migrations |
| 118 | **Initialize in constructor** - Use `blockConcurrencyWhile()` for schema setup only |
| 119 | **Use RPC methods** - Not fetch() handler (compatibility date >= 2024-04-03) |
| 120 | **Persist first, cache second** - Always write to storage before updating in-memory state |
| 121 | **One alarm per DO** - `setAlarm()` replaces any existing alarm |
| 122 | |
| 123 | ## Authorization |
| 124 | |
| 125 | Durable Objects do not have separate roles or permissions; access follows the Worker that implements them. Retrieve the current [Durable Objects authorization guidance] before granting observability or Data Studio access, and scope the Workers role to the intended Worker or Workers product. |
| 126 | |
| 127 | ## Anti-Patterns (NEVER) |
| 128 | |
| 129 | Single global DO handling all requests (bottleneck) |
| 130 | Using `blockConcurrencyWhile()` on every request (kills throughput) |
| 131 | Storing critical state only in memory (lost on eviction/crash) |
| 132 | Using `await` between related storage writes (breaks atomicity) |
| 133 | Holding `blockConcurrencyWhile()` across `fetch()` or external I/O |
| 134 | |
| 135 | ## Stub Creation |
| 136 | |
| 137 | |
| 138 | // Deterministic - preferred for most cases |
| 139 | const stub = env.MY_DO.getByName("room-123"); |
| 140 | |
| 141 | // From existing ID string |
| 142 | const id = env.MY_DO.idFromString(storedIdString); |
| 143 | const stub = env.MY_DO.get(id); |
| 144 | |
| 145 | // New unique ID - store mapping externally |
| 146 | const id = env.MY_DO.newUniqueId(); |
| 147 | const stub = env.MY_DO.get(id); |
| 148 | |
| 149 | |
| 150 | ## Storage Operations |
| 151 | |
| 152 | |
| 153 | // SQL (synchronous, recommended) |
| 154 | this.ctx.storage.sql.exec("INSERT INTO t (c) VALUES (?)", value); |
| 155 | const rows = this.ctx.storage.sql.exec<Row>("SELECT * FROM t").toArray(); |
| 156 | |
| 157 | // KV (async) |
| 158 | await this.ctx.storage.put("key", value); |
| 159 | const val = await this.ctx.storage.get<Type>("key"); |
| 160 | |
| 161 | |
| 162 | ## Alarms |
| 163 | |
| 164 | |
| 165 | // Schedule (replaces existing) |
| 166 | await this.ctx.storage.setAlarm(Date.now() + 60_000); |
| 167 | |
| 168 | // Handler |
| 169 | async alarm(): Promise<void> { |
| 170 | // Process scheduled work |
| 171 | // Optionally reschedule: await this.ctx.storage.setAlarm(...) |
| 172 | } |
| 173 | |
| 174 | // Cancel |
| 175 | await this.ctx.storage.deleteAlarm(); |
| 176 | |
| 177 | |
| 178 | ## Testing |
| 179 | |
| 180 | Read the [testing reference] before configuring a suite or writing Durable Object tests. It routes to current setup, APIs, and examples and identifies the behavior to cover. |
| 181 |
Details
- License
- Apache-2.0
- Stars on the repo
- ★ 2,976
- Checked
- Source
- cloudflare/skills ↗
- Creator
- cloudflare · 17 listings
Similar skills
See all →
SkillCross-Platform Reverse Engineer
SkillBrainstorm Ideas
SkillCode Review Feedback Analyzer
SkillBug Diagnosis Framework
SkillImplement Spec
SkillPR Description WriterMore from cloudflare
SkillSecurity Audit Reviewer
AgentCloudflare MCP Hub
SkillCloudflare Agents SDK Builder
SkillBasin Analytics Workflow
SkillCloudflare Email Config
SkillCloudflare K2 Stream Builder