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/vitest-pool-workers - Designing sharding strategies and parent-child relationships
Reference Documentation
./references/rules.md- Core rules, storage, concurrency, RPC, alarms./references/testing.md- Vitest setup, unit/integration tests, alarm testing./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
jsonc1// wrangler.jsonc 2{ 3 "durable_objects": { 4 "bindings": [{ "name": "MY_DO", "class_name": "MyDurableObject" }] 5 }, 6 "migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyDurableObject"] }] 7}
Basic Durable Object Pattern
typescript1import { DurableObject } from "cloudflare:workers"; 2 3export interface Env { 4 MY_DO: DurableObjectNamespace<MyDurableObject>; 5} 6 7export class MyDurableObject extends DurableObject<Env> { 8 constructor(ctx: DurableObjectState, env: Env) { 9 super(ctx, env); 10 ctx.blockConcurrencyWhile(async () => { 11 this.ctx.storage.sql.exec(` 12 CREATE TABLE IF NOT EXISTS items ( 13 id INTEGER PRIMARY KEY AUTOINCREMENT, 14 data TEXT NOT NULL 15 ) 16 `); 17 }); 18 } 19 20 async addItem(data: string): Promise<number> { 21 const result = this.ctx.storage.sql.exec<{ id: number }>( 22 "INSERT INTO items (data) VALUES (?) RETURNING id", 23 data 24 ); 25 return result.one().id; 26 } 27} 28 29export default { 30 async fetch(request: Request, env: Env): Promise<Response> { 31 const stub = env.MY_DO.getByName("my-instance"); 32 const id = await stub.addItem("hello"); 33 return Response.json({ id }); 34 }, 35};
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
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
typescript1// Deterministic - preferred for most cases 2const stub = env.MY_DO.getByName("room-123"); 3 4// From existing ID string 5const id = env.MY_DO.idFromString(storedIdString); 6const stub = env.MY_DO.get(id); 7 8// New unique ID - store mapping externally 9const id = env.MY_DO.newUniqueId(); 10const stub = env.MY_DO.get(id);
Storage Operations
typescript1// SQL (synchronous, recommended) 2this.ctx.storage.sql.exec("INSERT INTO t (c) VALUES (?)", value); 3const rows = this.ctx.storage.sql.exec<Row>("SELECT * FROM t").toArray(); 4 5// KV (async) 6await this.ctx.storage.put("key", value); 7const val = await this.ctx.storage.get<Type>("key");
Alarms
typescript1// Schedule (replaces existing) 2await this.ctx.storage.setAlarm(Date.now() + 60_000); 3 4// Handler 5async alarm(): Promise<void> { 6 // Process scheduled work 7 // Optionally reschedule: await this.ctx.storage.setAlarm(...) 8} 9 10// Cancel 11await this.ctx.storage.deleteAlarm();
Testing Quick Start
typescript1import { env } from "cloudflare:test"; 2import { describe, it, expect } from "vitest"; 3 4describe("MyDO", () => { 5 it("should work", async () => { 6 const stub = env.MY_DO.getByName("test"); 7 const result = await stub.addItem("test"); 8 expect(result).toBe(1); 9 }); 10});