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
./references/repo-conventions.md - This repo's Worker/DO conventions: DO call
retries, DO stub helpers, sub-module splitting, IO boundaries, and DB-client
lifecycle
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_classes in 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
await between related storage writes (breaks atomicity)
- Holding
blockConcurrencyWhile() across fetch() 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 Quick Start
import { env } from 'cloudflare:test';
import { describe, it, expect } from 'vitest';
describe('MyDO', () => {
it('should work', async () => {
const stub = env.MY_DO.getByName('test');
const result = await stub.addItem('test');
expect(result).toBe(1);
});
});
1---2name: durable-objects3description: Create and review Cloudflare Durable Objects. Use when building stateful coordination (chat rooms, multiplayer games, booking systems), implementing RPC methods, SQLite storage, alarms, WebSockets, or reviewing DO code for best practices. Covers Workers integration, wrangler config, and testing with Vitest. Biases towards retrieval from Cloudflare docs over pre-trained knowledge.4---56# Durable Objects78Build stateful, coordinated applications on Cloudflare's edge using Durable Objects.910## Retrieval Sources1112Your knowledge of Durable Objects APIs and configuration may be outdated. **Prefer retrieval over pre-training** for any Durable Objects task.1314| Resource | URL |15|---|---|16| Docs | https://developers.cloudflare.com/durable-objects/ |17| API Reference | https://developers.cloudflare.com/durable-objects/api/ |18| Best Practices | https://developers.cloudflare.com/durable-objects/best-practices/ |19| Examples | https://developers.cloudflare.com/durable-objects/examples/ |2021Fetch the relevant doc page when implementing features.2223## When to Use2425- Creating new Durable Object classes for stateful coordination26- Implementing RPC methods, alarms, or WebSocket handlers27- Reviewing existing DO code for best practices28- Configuring wrangler.jsonc/toml for DO bindings and migrations29- Writing tests with `@cloudflare/vitest-pool-workers`30- Designing sharding strategies and parent-child relationships3132## Reference Documentation3334- `./references/rules.md` - Core rules, storage, concurrency, RPC, alarms35- `./references/testing.md` - Vitest setup, unit/integration tests, alarm testing36- `./references/workers.md` - Workers handlers, types, wrangler config, observability37- `./references/repo-conventions.md` - This repo's Worker/DO conventions: DO call38 retries, DO stub helpers, sub-module splitting, IO boundaries, and DB-client39 lifecycle4041Search: `blockConcurrencyWhile`, `idFromName`, `getByName`, `setAlarm`, `sql.exec`4243## Core Principles4445### Use Durable Objects For4647| Need | Example |48|---|---|49| Coordination | Chat rooms, multiplayer games, collaborative docs |50| Strong consistency | Inventory, booking systems, turn-based games |51| Per-entity storage | Multi-tenant SaaS, per-user data |52| Persistent connections | WebSockets, real-time notifications |53| Scheduled work per entity | Subscription renewals, game timeouts |5455### Do NOT Use For5657- Stateless request handling (use plain Workers)58- Maximum global distribution needs59- High fan-out independent requests6061## Quick Reference6263### Wrangler Configuration6465```jsonc66// wrangler.jsonc67{68 "durable_objects": {69 "bindings": [{ "name": "MY_DO", "class_name": "MyDurableObject" }],70 },71 "migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyDurableObject"] }],72}73```7475### Basic Durable Object Pattern7677```typescript78import { DurableObject } from 'cloudflare:workers';7980export interface Env {81 MY_DO: DurableObjectNamespace<MyDurableObject>;82}8384export class MyDurableObject extends DurableObject<Env> {85 constructor(ctx: DurableObjectState, env: Env) {86 super(ctx, env);87 ctx.blockConcurrencyWhile(async () => {88 this.ctx.storage.sql.exec(`89 CREATE TABLE IF NOT EXISTS items (90 id INTEGER PRIMARY KEY AUTOINCREMENT,91 data TEXT NOT NULL92 )93 `);94 });95 }9697 async addItem(data: string): Promise<number> {98 const result = this.ctx.storage.sql.exec<{ id: number }>(99 'INSERT INTO items (data) VALUES (?) RETURNING id',100 data101 );102 return result.one().id;103 }104}105106export default {107 async fetch(request: Request, env: Env): Promise<Response> {108 const stub = env.MY_DO.getByName('my-instance');109 const id = await stub.addItem('hello');110 return Response.json({ id });111 },112};113```114115## Critical Rules1161171. **Model around coordination atoms** - One DO per chat room/game/user, not one global DO1182. **Use `getByName()` for deterministic routing** - Same input = same DO instance1193. **Use SQLite storage** - Configure `new_sqlite_classes` in migrations1204. **Initialize in constructor** - Use `blockConcurrencyWhile()` for schema setup only1215. **Use RPC methods** - Not fetch() handler (compatibility date >= 2024-04-03)1226. **Persist first, cache second** - Always write to storage before updating in-memory state1237. **One alarm per DO** - `setAlarm()` replaces any existing alarm124125## Anti-Patterns (NEVER)126127- Single global DO handling all requests (bottleneck)128- Using `blockConcurrencyWhile()` on every request (kills throughput)129- Storing critical state only in memory (lost on eviction/crash)130- Using `await` between related storage writes (breaks atomicity)131- Holding `blockConcurrencyWhile()` across `fetch()` or external I/O132133## Stub Creation134135```typescript136// Deterministic - preferred for most cases137const stub = env.MY_DO.getByName('room-123');138139// From existing ID string140const id = env.MY_DO.idFromString(storedIdString);141const stub = env.MY_DO.get(id);142143// New unique ID - store mapping externally144const id = env.MY_DO.newUniqueId();145const stub = env.MY_DO.get(id);146```147148## Storage Operations149150```typescript151// SQL (synchronous, recommended)152this.ctx.storage.sql.exec('INSERT INTO t (c) VALUES (?)', value);153const rows = this.ctx.storage.sql.exec<Row>('SELECT * FROM t').toArray();154155// KV (async)156await this.ctx.storage.put('key', value);157const val = await this.ctx.storage.get<Type>('key');158```159160## Alarms161162```typescript163// Schedule (replaces existing)164await this.ctx.storage.setAlarm(Date.now() + 60_000);165166// Handler167async alarm(): Promise<void> {168 // Process scheduled work169 // Optionally reschedule: await this.ctx.storage.setAlarm(...)170}171172// Cancel173await this.ctx.storage.deleteAlarm();174```175176## Testing Quick Start177178```typescript179import { env } from 'cloudflare:test';180import { describe, it, expect } from 'vitest';181182describe('MyDO', () => {183 it('should work', async () => {184 const stub = env.MY_DO.getByName('test');185 const result = await stub.addItem('test');186 expect(result).toBe(1);187 });188});189```