Workers are great at answering HTTP.
A real app often needs more:
- a chat room
- a cart
- real-time presence
- rate limits per API key
- an AI agent per conversation
- a collaborative document
In every case the same question shows up:
Who remembers state for this entity and coordinates what happens to it?
That’s what Durable Objects are for.
What a Durable Object is
A Durable Object is a special kind of Worker that combines compute + storage.
In plain terms:
an entity with its own identity, runnable code, and persistent state.
Keep the product name in English: Durable Object. After you define it, DO is fine. “Durable object” as a vague phrase doesn’t help in a team conversation.
Mental model: the key is identity
Think in entities:
room:general
cart:user_123
counter:foo
agent:thread_456
Each key points at a specific instance.
- Same key → same Durable Object
- Different keys → different objects
counter:foo -> DO for foo
counter:bar -> DO for bar
That’s the point: identity.
It’s not just a database
A database stores data.
A Durable Object can store data and run logic for one concrete entity.
Example: a chat room. You don’t only need messages. You may also need:
- who’s connected
- broadcast to active sockets
- event ordering
- fewer race conditions inside that entity
- cleanup after idle time
That kind of coordination is awkward if everything is loose stateless functions plus a generic table.
Example: counter per key
We want:
/increment?name=foo
/increment?name=bar
foo and bar don’t share a counter.
import { DurableObject } from "cloudflare:workers";
export interface Env {
COUNTERS: DurableObjectNamespace<Counter>;
}
export class Counter extends DurableObject<Env> {
async getValue(): Promise<number> {
return (await this.ctx.storage.get<number>("value")) ?? 0;
}
async increment(): Promise<number> {
const current = await this.getValue();
const next = current + 1;
await this.ctx.storage.put("value", next);
return next;
}
}
export default {
async fetch(request, env): Promise<Response> {
const url = new URL(request.url);
const name = url.searchParams.get("name");
if (!name) {
return new Response("Missing ?name=foo", { status: 400 });
}
const id = env.COUNTERS.idFromName(name);
const counter = env.COUNTERS.get(id);
const value = await counter.increment();
return Response.json({ name, value });
},
} satisfies ExportedHandler<Env>;
The key part:
const id = env.COUNTERS.idFromName(name);
const counter = env.COUNTERS.get(id);
name decides identity. The binding COUNTERS is the capability; the key is the instance.
Request flow
For ?name=foo:
request
-> Worker
-> idFromName("foo")
-> Durable Object foo
-> read state
-> increment
-> save
-> respond
Another request with foo hits the same DO. One with bar goes elsewhere.
Good fits
| Case | Typical key |
|---|---|
| Chat rooms | room:{roomId} |
| Carts | cart:{sessionId} |
| Per-entity rate limit | limit:{apiKey} |
| AI agents | agent:{threadId} |
In each case the unit of coordination is obvious: room, session, key, thread.
Common failure: one global DO for everything
global:app
If every request goes through one global entity, you get an elegant bottleneck.
Durable Objects work better when you split by entity:
room:{roomId}
cart:{sessionId}
tenant:{tenantId}
agent:{threadId}
Design question:
What’s the natural unit of coordination?
When I would NOT use Durable Objects
I wouldn’t use one just because “I need to store something.”
| You need | Look first |
|---|---|
| SQL | D1 |
| Object storage | R2 |
| Simple key-value | KV |
| Async processing | Queues |
I’d use a DO when you need all three together:
identity + state + coordination
Design checklist
Before you create a DO, answer:
- What’s the entity?
- How is the key built?
- What state does it hold?
- What methods does it expose?
- What happens if there are many objects?
- What happens when it wakes after idle?
- What logs do you need per entity?
- What stays on the Worker vs the DO?
Readable design example:
Entity: support room
Key: support-room:{accountId}
Worker route: /rooms/:accountId/message
DO methods: addMessage, connectUser, getRecentMessages
Async: Queue for analytics
Now the system starts to make sense.
The line to remember
Worker = HTTP entry
Durable Object = entity with state
key = object identity
If your problem sounds like “I need to coordinate a room, cart, document, tenant, or agent,” Durable Objects deserve a seat at the table.
Cloudflare series for builders: Workers · Bindings · Durable Objects · Queues
Docs: Durable Objects · Examples · Bindings