tech/cloudflare/durable-objects

DURABLE OBJECTS

Cloudflare Durable Objects — stateful edge compute.

production Cloudflare Workers (Paid plan required)
improves: tech/cloudflare

Cloudflare Durable Objects

Durable Objects (DOs) are single-instance, globally-unique JavaScript objects with persistent storage and a co-located execution environment. Unlike Workers (which are stateless isolates), each DO instance:

Requires Workers Paid plan.

wrangler.toml

[[durable_objects.bindings]]
name = "CHAT_ROOM"
class_name = "ChatRoom"

# Must also declare the migration
[[migrations]]
tag = "v1"
new_classes = ["ChatRoom"]

Durable Object class

import { DurableObject } from 'cloudflare:workers';

export class ChatRoom extends DurableObject {
  private sessions: Map<WebSocket, { userId: string }> = new Map();

  async fetch(request: Request): Promise<Response> {
    const upgradeHeader = request.headers.get('Upgrade');
    if (upgradeHeader === 'websocket') {
      return this.handleWebSocket(request);
    }
    return new Response('Expected WebSocket', { status: 426 });
  }

  private async handleWebSocket(request: Request): Promise<Response> {
    const [client, server] = Object.values(new WebSocketPair());

    server.addEventListener('message', async (event) => {
      const data = JSON.parse(event.data as string);
      await this.broadcast(data);
    });

    server.addEventListener('close', () => {
      this.sessions.delete(server);
    });

    this.ctx.acceptWebSocket(server);
    this.sessions.set(server, { userId: new URL(request.url).searchParams.get('userId') ?? 'anon' });

    return new Response(null, { status: 101, webSocket: client });
  }

  private async broadcast(message: unknown): Promise<void> {
    const payload = JSON.stringify(message);
    for (const ws of this.sessions.keys()) {
      try { ws.send(payload); } catch { this.sessions.delete(ws); }
    }
  }
}

Getting a DO stub from a Worker

// By name — same name always routes to same instance
const id = env.CHAT_ROOM.idFromName('room:project-123');
const stub = env.CHAT_ROOM.get(id);
const response = await stub.fetch(request);

// By generated ID — unique per creation
const id = env.CHAT_ROOM.newUniqueId();
const stub = env.CHAT_ROOM.get(id);

Persistent storage (transactional KV)

export class Counter extends DurableObject {
  async increment(amount = 1): Promise<number> {
    const current = (await this.ctx.storage.get<number>('count')) ?? 0;
    const next = current + amount;
    await this.ctx.storage.put('count', next);
    return next;
  }

  // Atomic transaction
  async transfer(from: string, to: string, amount: number): Promise<void> {
    await this.ctx.storage.transaction(async (txn) => {
      const fromBalance = (await txn.get<number>(from)) ?? 0;
      if (fromBalance < amount) throw new Error('Insufficient funds');
      await txn.put(from, fromBalance - amount);
      await txn.put(to, ((await txn.get<number>(to)) ?? 0) + amount);
    });
  }
}

Streaming AI output via DO + WebSocket

// DO streams Workers AI output to all connected WebSocket clients
async streamAI(prompt: string): Promise<void> {
  const stream = await this.env.AI.run('@cf/meta/llama-3.1-8b-instruct', {
    messages: [{ role: 'user', content: prompt }],
    stream: true,
  });

  const reader = stream.getReader();
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    this.broadcast({ type: 'chunk', text: new TextDecoder().decode(value) });
  }
  this.broadcast({ type: 'done' });
}

Alarms (scheduled tasks within a DO)

export class AgentSession extends DurableObject {
  async setAlarm(delayMs: number): Promise<void> {
    await this.ctx.storage.setAlarm(Date.now() + delayMs);
  }

  // Called automatically when alarm fires
  async alarm(): Promise<void> {
    await this.cleanupExpiredSessions();
  }
}

Common Gotchas

When to use DOs vs alternatives

NeedUse
Real-time collaboration (WebSockets)Durable Objects
Atomic counter / rate limiterDurable Objects
Async background jobsQueues
Shared config/flagsKV
Per-user session dataKV (with TTL)
Structured data + SQL queriesD1

See Also