Skip to content
RT
Go back

Durable Objects: identity, state, and coordination

Workers are great at answering HTTP.

A real app often needs more:

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.

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:

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

CaseTypical key
Chat roomsroom:{roomId}
Cartscart:{sessionId}
Per-entity rate limitlimit:{apiKey}
AI agentsagent:{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 needLook first
SQLD1
Object storageR2
Simple key-valueKV
Async processingQueues

I’d use a DO when you need all three together:

identity + state + coordination

Design checklist

Before you create a DO, answer:

  1. What’s the entity?
  2. How is the key built?
  3. What state does it hold?
  4. What methods does it expose?
  5. What happens if there are many objects?
  6. What happens when it wakes after idle?
  7. What logs do you need per entity?
  8. 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


Share this post on:

Previous Post
How I start indie projects: Better-T-Stack and the tools inside it
Next Post
Cloudflare Workers bindings: permission + API in env