TL;DR:

  • Durable Objects are Cloudflare’s solution for stateful computation at the edge — each object has its own persistent storage, runs in a single location, and handles concurrency automatically.
  • They’re the right tool for real-time features that need coordination: multiplayer state, collaborative editing, rate limiting, chat rooms, gaming sessions.
  • Durable Objects pair naturally with WebSocket hibernation and Cloudflare Queues for scalable real-time architectures that run globally without managing servers.

Serverless edge computing is great at stateless operations — caching, routing, transforming requests, running logic close to the user. It becomes much harder when you need to coordinate state: two users editing the same document, a rate limiter that counts requests across a fleet, a multiplayer game that needs a shared source of truth.

Cloudflare Durable Objects exist to solve this problem. They’re the part of the Workers platform that most developers underuse, often because the mental model takes a few minutes to internalize — after which it’s surprisingly intuitive.

The Core Concept

A Durable Object is a JavaScript/TypeScript class that Cloudflare runs as a singleton. Every Durable Object instance has:

  • A unique ID (user-defined or auto-generated)
  • Built-in transactional storage (key-value, SQLite, or blob)
  • A single execution location at any given time
  • A WebSocket server if you need persistent connections

The key property is that each Durable Object instance runs in exactly one location at a time. If two requests come in for the same object from different parts of the world, they both get routed to the same instance. This is how Durable Objects provide consistency — there’s only one copy.

This is different from a regular Worker, which runs in parallel across Cloudflare’s network, with no shared state between invocations.

A Simple Example

Here’s a counter that maintains state across requests:

export class Counter extends DurableObject {
  count: number = 0;

  constructor(state: DurableObjectState, env: Env) {
    super(state, env);
  }

  async fetch(request: Request): Promise<Response> {
    this.count++;
    await this.ctx.storage.put("count", this.count);
    return new Response(`Count: ${this.count}`);
  }
}

Every request to this object — from anywhere in the world — goes to the same instance, increments the same counter, and reads back a consistent value. No Redis, no database, no distributed locking.

From a Worker, you access the Durable Object by ID:

const id = env.COUNTER.idFromName("global-counter");
const stub = env.COUNTER.get(id);
const response = await stub.fetch(request);

The idFromName call is deterministic — the same name always produces the same ID, so you can address objects by semantic name rather than managing IDs yourself.

Storage Options

Durable Objects support three storage backends, chosen by how you access data:

Key-value storage (this.ctx.storage.get/put/delete) works like a fast, consistent map. Best for simple state: counters, flags, session data, small documents.

SQLite storage (this.ctx.storage.sql) gives you a full SQLite database per Durable Object instance. Run queries against it with full SQL — joins, aggregations, transactions. This is the right choice when your per-object state is complex enough to warrant a relational model.

Blob storage for larger binary data, with references stored in key-value or SQLite.

Writes to Durable Object storage are automatically batched and applied transactionally. You don’t need to think about write ordering or partial failures within a single object.

WebSockets and Real-Time

The feature that makes Durable Objects compelling for real-time applications is built-in WebSocket support with hibernation.

Without hibernation, a WebSocket connection keeps a Worker alive indefinitely — consuming CPU and billing accordingly. Durable Objects with hibernation can suspend a connection when there’s no activity, wake up on the next message, and maintain the connection state without the cost of keeping the object in memory.

For a collaborative document with 50 active users, hibernation means you’re paying for CPU only when messages are actually being processed, not for the idle time between keystrokes.

A basic WebSocket server with Durable Objects:

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

  async fetch(request: Request) {
    const pair = new WebSocketPair();
    const [client, server] = Object.values(pair);
    
    this.ctx.acceptWebSocket(server);
    return new Response(null, { status: 101, webSocket: client });
  }

  webSocketMessage(ws: WebSocket, message: string) {
    // Broadcast to all connected clients
    for (const [session] of this.sessions) {
      session.send(message);
    }
  }

  webSocketClose(ws: WebSocket) {
    this.sessions.delete(ws);
  }
}

All WebSocket connections to the same room land at the same Durable Object instance. Messages broadcast from one client go to all others without any pub/sub infrastructure.

Real-World Use Cases

Rate limiting. Each Durable Object can represent a rate limit bucket — a user, an IP, an API key. The object tracks request counts and timestamps, and returns allow/deny decisions. Because it’s a singleton, there’s no double-counting across distributed Workers.

Collaborative editing. Operational transformation or CRDT logic runs inside the object. Multiple clients send edits via WebSocket; the object applies them in order and broadcasts the result. No coordination infrastructure needed.

Multiplayer game sessions. Each game room is a Durable Object. Players connect via WebSocket. The object holds the authoritative game state. When a player disconnects mid-game, the state persists.

Presence indicators. Track who’s viewing a document or channel. The object maintains a set of active connections; when a connection closes, the presence disappears automatically.

Distributed locks. When you need mutual exclusion across Workers (protecting a shared resource, coordinating scheduled work), a Durable Object is a clean primitive for this — request the lock by sending to the object, release it when done.

Limits and Tradeoffs

Durable Objects aren’t the right tool for everything. Key constraints:

  • Location: an object lives in one region at a time. If most of your users are in one geography, this is fine. If you need globally distributed writes with low latency everywhere, you’ll need a different architecture (or multiple objects with eventual consistency).

  • Single-threaded: each object processes one request at a time, in order. This gives you consistency without locks, but means very high concurrency on a single object creates a queue. Design your objects so that load is distributed across many instances, not concentrated on one.

  • Storage limits: individual Durable Object instances support up to 10 GB of storage. For most stateful workloads this is more than enough, but it’s not a general-purpose database.

  • Cold starts: if an object hasn’t been accessed recently, the first request after a period of inactivity will be slower (tens of milliseconds) while the object wakes up. Hibernation helps with WebSocket use cases; for other patterns, factor this into latency expectations.

Integrating with the Cloudflare Ecosystem

Durable Objects compose well with the rest of the Workers platform:

Workers handle the stateless layer — routing, authentication, caching. They hand off to Durable Objects only when state coordination is needed.

Cloudflare Queues provide a way to send messages to a Durable Object asynchronously, useful when you want to fan out work or decouple producers from consumers.

R2 (object storage) works alongside Durable Object storage for large files. Store metadata in the Durable Object, blobs in R2.

D1 (SQLite at the edge) is an alternative to per-object SQLite when you need a shared relational store rather than per-object storage. The choice depends on whether your state is naturally partitioned (Durable Objects) or shared (D1).

Getting Started

Durable Objects are available on the Cloudflare Workers Paid plan. To define one, add a durable_objects binding in your wrangler.toml:

[[durable_objects.bindings]]
name = "COUNTER"
class_name = "Counter"

[[migrations]]
tag = "v1"
new_classes = ["Counter"]

Then export your class from the entry point and Cloudflare handles the routing automatically.

The official documentation is thorough, and the open-source partykit library provides higher-level abstractions for the most common real-time patterns if you’d rather not build from primitives.


Durable Objects are one of the more elegant pieces of the modern edge computing stack. The mental model takes a few minutes, but once it clicks, you’ll find yourself reaching for them whenever you need coordination — and realising how much infrastructure they eliminate.