Cloudflare Workers · Durable Objects

A tiny server for every thing you care about.

Durable Objects give each chat room, document, game match, auction, or user its own stateful server — exactly one instance, anywhere on Earth, with storage built in. It's the fastest way to ship real-time, multiplayer, and coordinated apps.

Free tier: 100,000 requests and 13,000 GB-s of duration per day. No credit card required.

1 instanceper thing, globally — guaranteed by the network
128 MB + SQLitememory and an embedded database per object
Millionsof objects per account — zero servers to manage
Three different “things,” three independent objects. Requests for the same thing always land on the same instance — from anywhere.

The 30-second version

What is a Durable Object, actually?

Most explanations start with “serverless compute with strongly consistent coordination semantics.” Let's not.

A Durable Object is a tiny stateful server that exists exactly once, globally, for each thing you care about.

One per chat room. One per document. One per user, auction, job, or AI-agent session. You never launch or patch these servers — you address them by name, and Cloudflare guarantees that the right one, the only one, answers. When nobody's using it, it goes to sleep. When a request arrives, it wakes in milliseconds with its data still there.

01

Give every “thing” its own server

A multiplayer game isn't one giant server — it's thousands of tiny ones, one per match. Here the unit of scale is the thing itself: ROOMS.idFromName("match-8812") creates-or-reuses exactly that match's object. First.

02

The server remembers

Each object carries memory and an embedded SQLite database in the same process. State isn't a network hop away in someone else's cluster — it's a local variable and a local table, right next to your code.

03

The network does the hard part

Every request for a thing — arriving at any of Cloudflare's 330+ city networks — routes to that thing's one object. Uniqueness, placement, failover, and wake-ups are the platform's job. Yours is the product.

If you remember one sentence: a Worker is a stateless function that can run anywhere; a Durable Object is a stateful actor that runs in exactly one place. That single difference deletes an entire category of infrastructure — locks, caches, pub/sub glue, and “who owns this row?” arguments.

Interactive

One object, no race conditions.

The hard part of stateful apps isn't storing data — it's keeping one version of the truth when everyone writes at once. Watch the same two edits land in a traditional stateless stack versus in a Durable Object. Then see where the object lives.

The usual way stateless + shared DB
Stateless workers + one shared database. Each request lands on some worker; state lives far away. Press “Send concurrent edits” to see what breaks.
The Durable Object way one object per thing
Every request for this document reaches the same object. Edits are handled one at a time, in arrival order, against state that's right there. No locks, no races.
Two users, 20 ms apart, editing the same line of “Q3-plan”.

Under the hood, in plain language

Five ideas that make it work.

You have the mental model. Here's what's actually happening — each mechanic, the problem it deletes, and the code you'd otherwise have to write.

01

Global uniqueness — the object is the lock

Two requests, same thing, same instance. Always.

A Durable Object class defines a kind of thing. Names identify individual things. idFromName("room-42") hashes the name into an ID that maps to exactly one live instance — in every Cloudflare location, for the lifetime of the thing.

That's why the docs call a Durable Object a single point of coordination. You don't take a lock to guard shared state; being the only writer is the lock. Two users opening “room-42” from opposite sides of the planet are, provably, talking to the same process.

Uniqueness is enforced by the router, not by your discipline: there is no flag, region, or race condition that can produce a second instance of the same ID.

🇺🇸 client (SFO) 🇩🇪 client (FRA) 🇯🇵 client (NRT) idFromName("room-42") DO · room-42 the only instance
Same name, any region → same ID → same instance.

worker.ts — routing by name (the only “config” you need)

// Any Worker, anywhere, can reach the same object by name:
export default {
  async fetch(req, env) {
    const url = new URL(req.url);
    const room = url.searchParams.get("room") ?? "lobby";

    // Same name → same ID, in every region, forever.
    const id = env.CHAT_ROOMS.idFromName(room);
    const stub = env.CHAT_ROOMS.get(id);

    // Forward the request to that room's one instance.
    return stub.fetch(req);
  },
};
02

Single-threaded execution — races can't happen

Events arrive one at a time. No mutexes, no interleaving.

Inside an object, the runtime delivers one event at a time. While your code runs — or while a storage write is in flight — no other event is delivered (Cloudflare calls these input gates). The next request, WebSocket message, or alarm waits its turn.

The consequence is bigger than it sounds: this.value++; await save(); is correct because nothing else can run between those lines. The entire taxonomy of read-modify-write races — the reason you'd reach for locks, version numbers, or transactions — doesn't apply inside an object.

Caveat for honesty: single-threaded also means one hot object has a throughput ceiling. The scale-out answer is more objects — one per thing — not bigger ones. See the honest comparison.

waiting events single thread input gate: one at a time done ✓
Events serialize through one gate — interleaving is impossible by construction.

counter.ts — a correct counter with zero locking

import { DurableObject } from "cloudflare:workers";

export class Counter extends DurableObject {
  // In-memory state is safe: only one event runs at a time.
  count = 0;

  async increment(amount = 1) {
    this.count += amount;              // read-modify-write, no lock
    await this.ctx.storage.put("count", this.count); // durable
    return this.count;
  }
}
03

Colocated storage — no network hop

The database lives in the same process as your code.

In a stateless stack, state lives somewhere else: a central Postgres, a Redis node, a cache tier. Every request pays a network round-trip to reach it — and pays again to stay consistent.

A Durable Object's storage is embedded in the object itself. Reads and writes are local calls from your handler into SQLite — no connection pool, no VPC, no “cache stampede.” When the object moves or restarts, its data moves with it.

You get two storage APIs on the same data: familiar SQL (tables, indexes, transactions) and a tiny key-value API for quick durable flags and blobs.

stateless stack compute database network hop: 1–50 ms, pool, retry, consistency… state far from code durable object your code SQLite in-process: microseconds, no pool, no extra retry path state lives with compute
Same machine, same process: the “database call” never leaves the object.

storage.ts — SQL and KV, both local

export class DocStore extends DurableObject {
  async save(docId: string, body: string) {
    // SQL with parameters, indexes, transactions — in-process.
    this.ctx.storage.sql.exec(
      `INSERT INTO versions (doc_id, body, at)
       VALUES (?, ?, datetime('now'))`,
      docId, body,
    );
  }

  async pin(key: string, value: unknown) {
    // Or the KV-style API for simple durable state.
    await this.ctx.storage.put(key, value);
  }
}
04

WebSocket hibernation — sleep while connected

Sockets stay open while the object bills $0.

A chat room with 500 idle users shouldn't cost like a busy server. With the Hibernation API, an object with no incoming events is evicted from memory while its WebSockets stay connected — the Cloudflare network holds the sockets.

When the next message arrives, the object re-initializes in milliseconds, restyles its state from SQLite (or per-socket attachments), and carries on. While it sleeps, no duration (GB-s) charges accrue — you pay for messages and requests, not for holding connections open.

Even asleep, the object can answer protocol pings via setWebSocketAutoResponse() — zero wake-up, zero billing for keepalives.

active — billing hibernating — $0 duration wake in ms message arrives sockets stay connected throughout — held by the edge, not the object duration billed only while active · idle rooms cost ≈ storage, not servers
Hibernate without dropping a single client.

hibernate.ts — the whole “fleet” trick

import { DurableObject } from "cloudflare:workers";

export class Room extends DurableObject {
  async fetch(req: Request) {
    const [client, server] = Object.values(new WebSocketPair());
    // Hibernating accept — the object may sleep after this:
    this.ctx.acceptWebSocket(server);
    return new Response(null, { status: 101, webSocket: client });
  }

  // Runs only when a message actually arrives (object wakes if needed).
  async webSocketMessage(ws: WebSocket, msg: string) {
    const user = this.ctx.getWebSockets().indexOf(ws);
    this.broadcast(`${user ?? "guest"}: ${msg}`);
  }

  broadcast(text: string) {
    for (const ws of this.ctx.getWebSockets()) ws.send(text);
  }
}
05

SQLite-backed state — a real database per thing

Schema, indexes, transactions — one per object.

Every object ships with its own SQLite database. That means CREATE TABLE, indexes, constraints, joins, and transactions — not just get/put. Because each thing has its own database, you get per-tenant isolation for free: tenant A's queries physically cannot touch tenant B's rows.

SQLite is the storage backend Cloudflare recommends for every new Durable Object class, and it's the only backend available on the Free plan — so what you prototype is what you run.

Storage is billed by what you use: rows read, rows written, and bytes stored — see pricing.

class ChatRoom one schema, defined once #general → SQLite 12k messages #design → SQLite 980 messages #random → SQLite 51k messages
One class → many objects, each with an isolated database.

schema.ts — real SQL, once per object

export class ChatRoom extends DurableObject {
  async init() {
    this.ctx.storage.sql.exec(`
      CREATE TABLE IF NOT EXISTS messages (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        user TEXT NOT NULL,
        body TEXT NOT NULL,
        at TEXT DEFAULT (datetime('now'))
      );
      CREATE INDEX IF NOT EXISTS idx_messages_at ON messages(at);
    `);
  }

  history(limit = 50) {
    // Prepared statement, streamed rows — locally, in-process.
    return this.ctx.storage.sql
      .prepare("SELECT user, body, at FROM messages ORDER BY id DESC LIMIT ?")
      .all(limit);
  }
}

Where Durable Objects shine

Seven problems that stop hurting.

For each: where the stateless-workers-plus-Redis approach strains, what goes wrong as it grows — and what the Durable Object version looks like instead.

Real-time collaboration and multiplayer

Shared documents, whiteboards, and game sessions are the canonical Durable Object workload: several people mutating one live thing, everyone needing to see everyone else's changes instantly — and the result to be correct.

Where the old way strains

  • You bolt a CRDT library onto stateless workers and a pub/sub broker — then conflict resolution becomes the product.
  • Presence stored in Redis with TTLs flickers on reconnect; cursors and selections drift.
  • When one document gets hot, you're hand-sharding rooms and pinning users to shards.

The Durable Object way

  • One object per document is the single sequencer: edits apply in arrival order, so last-writer-wins is correct, not a compromise.
  • Presence is a Map in memory, broadcast on every change; the version history is a local SQLite table.
  • Popular doc? It's still one object — and the other million docs don't care.

collab.ts — the whole multiplayer core

export class Whiteboard extends DurableObject {
  strokes: Stroke[] = [];                 // authoritative, in-memory

  async fetch(req: Request) {
    const url = new URL(req.url);
    const [client, server] = Object.values(new WebSocketPair());
    this.ctx.acceptWebSocket(server, [url.searchParams.get("user") ?? "guest"]);
    server.send(JSON.stringify({ type: "init", strokes: this.strokes }));
    return new Response(null, { status: 101, webSocket: client });
  }

  async webSocketMessage(ws: WebSocket, raw: string) {
    const stroke = JSON.parse(raw);
    this.strokes.push(stroke);            // serialized: no two-at-once
    this.ctx.storage.sql.exec(
      "INSERT INTO strokes (id, points) VALUES (?, ?)",
      stroke.id, JSON.stringify(stroke.points),
    );
    this.broadcastBut(ws, raw);           // everyone else draws it live
  }

  broadcastBut(sender: WebSocket, text: string) {
    for (const peer of this.ctx.getWebSockets()) if (peer !== sender) peer.send(text);
  }
}

Developer experience

It reads like a class, because it is one.

No topology YAML, no broker configuration, no connection-pool tuning. You write a TypeScript (or Python) class with a fetch handler and storage on this.ctx — then ship it with one command.

Scaffold the project

Wrangler — Cloudflare's CLI — generates a working Durable Objects starter with tests and types included.

npm create cloudflare@latest my-app

Write the class

Extend DurableObject. Handle requests in fetch(), sockets in webSocketMessage(), persistence with this.ctx.storage. That's the API surface.

export class Chat extends DurableObject { … }

Deploy to the planet

One command pushes your Worker, its bindings, and the Durable Object classes it declares. Objects appear as traffic asks for them.

npx wrangler deploy

Prefer higher-level abstractions? The open-source @cloudflare/actors library adds actor-style patterns (entities, singletons, scheduling) on top — and the Cloudflare Agents SDK builds full agent runtime on the same primitive.

a counter — the “hello world” of state

import { DurableObject } from "cloudflare:workers";

interface Env {
  COUNTERS: DurableObjectNamespace;
}

export class Counter extends DurableObject {
  async fetch(request: Request): Promise {
    let current = (await this.ctx.storage.get("value")) ?? 0;
    const url = new URL(request.url);

    // Update the value based on the request path.
    switch (url.pathname) {
      case "/increment":
        current += 1;
        await this.ctx.storage.put("value", current);
        break;
      case "/decrement":
        current -= 1;
        await this.ctx.storage.put("value", current);
        break;
    }

    // Return the current value as a plain string.
    return new Response(String(current));
  }
}

export default {
  async fetch(request: Request, env: Env) {
    // Route to a unique instance based on the request path:
    // /counter/foo and /counter/bar are two independent counters.
    const name = new URL(request.url).pathname.split("/").pop() ?? "default";
    const id = env.COUNTERS.idFromName(name);
    return env.COUNTERS.get(id).fetch(request);
  },
};

Also available in Python (currently in beta) — same classes, same storage, same deploy.

Honest comparison

Durable Objects vs. the stack you'd otherwise build.

The conventional answer is stateless workers plus a database (and usually Redis). The ambitious answer is your own stateful fleet. Both genuinely work — here's what each costs you, and where Durable Objects are the wrong tool.

Stateless workers + DB/Redis DIY stateful fleet Durable Objects
Where state lives A network hop away: central Postgres/Redis, plus cache tiers to soften it. On machines you own and replicate — your ops problem now. Inside the object: memory + embedded SQLite in one process.
Two users, one entity the classic race Optimistic concurrency: locks, version columns, retries — correct eventually, with engineering. Whatever you built. Some fleets pin entities to nodes; most discover races in production. Serialized by a single thread. The race cannot occur.
WebSockets Bolted on: sticky sessions, a gateway product, or both. Native, but you size the fleet for peak connections and pray at deploy time. Native, with hibernation: idle sockets stay connected at ~zero cost.
Idle cost Database and cache run 24/7; per-connection servers idle at full price. The fleet you sized for Friday night runs all Sunday. Hibernating objects bill no duration — you pay for storage and actual activity.
Scaling model Scale the workers — then shard the hot entity anyway, by hand. Manual sharding, rebalancing, drain-and-deploy cycles. Scale by having more things. Each object stays simple; nothing is rebalanced.
Ops burden Manage DB failover, capacity, and the cache-invalidation folklore. All of it: patching, autoscaling groups, load balancers, on-call. Zero servers. Placement, failover, and wake-up are the platform's.
Single-entity ceiling Scales far — that's the point (with eventual consistency as the price). Scales if you engineer it to. One object is single-threaded: a very hot single thing has a cap. The pattern is more objects, not bigger ones.
Time to working prototype Days to weeks: schema, locking, cache, broker. Weeks — infrastructure before product. Minutes: one class, one binding, one deploy.

When Durable Objects are the wrong tool

A honest page says this out loud. Reach elsewhere when:

Big blobs and media

Files, images, video, model weights: store them in R2 (S3-compatible, zero egress fees) and keep only references in the object.

One shared relational database

When many callers need ad-hoc SQL across all entities at once, that's D1 or your existing warehouse — not thousands of per-thing databases.

Long, CPU-heavy work

Sustained number-crunching beyond Worker CPU limits belongs in Containers — which can still use a Durable Object as its coordinator and state.

Pricing & availability

Pay for activity, not for idle servers.

Durable Objects run on the Workers Free and Workers Paid plans. Hibernation means quiet things cost almost nothing — the bill follows traffic, not fleet size.

Workers Free

$0/ month, forever
  • 100,000 requests / day (resets 00:00 UTC)
  • 13,000 GB-s duration / day — idle objects don't consume it
  • 5 GB SQLite storage included
  • SQLite-backed classes (the recommended, current backend)

Start free

DimensionFree planPaid plan
Requests (HTTP, RPC sessions, billed WS messages, alarms)100,000 / day1M / month included, then $0.15 / million
Duration (wall-clock while active; 128 MB memory basis)13,000 GB-s / day400,000 GB-s / month included, then $12.50 / million GB-s
SQL rows read$0.001 / million
SQL rows written$1.00 / million
SQLite data stored5 GB included$0.20 / GB-month

Billing details worth knowing: incoming WebSocket messages are billed at a 20:1 ratio (small real-time messages), outgoing messages and protocol pings are free, and auto-response pings via setWebSocketAutoResponse() don't wake the object. Hibernating objects accrue no duration charges — that's the point of hibernation. Daily free limits reset at 00:00 UTC. Full detail, with worked examples, lives in the pricing documentation.

Social proof & ecosystem

Teams already run their real-time products this way.

Cloudflare released Durable Objects at just the right time for us. Without Cloudflare, hosting WebSocket servers might have required at least four additional people just for management. Using Durable Objects, we can provide serverless capabilities without a dedicated team.
Steve Lloyd Co-founder & CTO, Liveblocks
Real-time teams building on Cloudflare

FAQ

Questions newcomers actually ask.

Is a Durable Object a server I have to manage?

No. It's serverless: you declare a class in your Worker project and deploy. Cloudflare places instances, routes requests to the right one, restarts them after failures, and puts them to sleep when idle. There is no instance list to babysit — objects exist because things (rooms, documents, users) exist.

What happens if my object gets too busy?

A single object is single-threaded, so one very hot thing has a throughput ceiling — that's the trade that buys you race-freedom. The documented scale-out pattern is more objects: shard a busy room into sub-rooms, a global rate limit into per-region buckets with a global reconciler, a hot queue into one object per worker shard. Each shard stays simple; the platform handles the fleet.

How is this different from Workers KV, D1, or R2?

All four store data; they differ in shape. KV is an eventually-consistent global key-value cache — great for config, wrong for coordination. D1 is one conventional Postgres-style relational database for queries across all your data. R2 is object storage for big blobs. A Durable Object is the only one that combines compute with strongly-consistent, per-entity state — the thing you reach for when “two requests must agree on one version of the truth.”

What happens to in-memory state when the object sleeps or moves?

In-memory variables are a cache, not a promise: when the object hibernates, migrates, or restarts, RAM resets and the constructor runs again on the next event. Anything that must survive goes through this.ctx.storage — the embedded SQLite — or per-socket attachments for WebSocket state. The standard pattern: load lazily on first use, write-through on change.

Can I really run this on the free plan?

Yes. Durable Objects are available on Workers Free with SQLite-backed classes: 100,000 requests and 13,000 GB-s of duration per day, plus 5 GB of storage. That's thousands of quiet chat rooms or a full hobby multiplayer game. The key-value storage backend is the legacy option, available only on paid plans for accounts that already used it.

Which languages can I use?

JavaScript and TypeScript are first-class. Python is supported in beta — the same class model, storage API, and deploy path. Everything on this page (bindings, migrations, hibernation) works from either.

Do clients connect directly to objects?

Objects aren't directly addressable from the public internet. A Worker sits in front: it authenticates, derives the object ID from something meaningful in the request (a room slug, a tenant host, a user ID), and forwards via a stub — or calls methods over type-safe RPC. That indirection is a feature: routing and authorization live in ordinary code you control.

What if the machine holding my object dies?

The runtime notices and starts a new instance of the object elsewhere — near users, per its placement — restoring from the replicated storage backend. Clients reconnect through the edge (or never notice, if they're on hibernating WebSockets held by the network). You lose at most in-flight, un-persisted memory — the same discipline as any actor model: persist what matters, treat RAM as a cache.

Build real-time apps your users can feel.

One class, one deploy, one less distributed-systems degree to earn. Start on the free plan — your first Durable Object can be live before your coffee cools.

Cloudflare Workers “Start building” card npx wrangler deploy → live in seconds
View more demos Get up to 40% off GLM-5.3