Blog21 min read
Stateful APIs at the edge with Durable Objects
Deep Durable Objects guide on Cloudflare - IDs, storage, SQLite, alarms, WebSockets, when not to use DOs, and experiments from counter to chat.
Stateless Workers scale easily, but some features need strongly consistent state: counters, presence, turn-based games, per-document locks, or chat rooms. Durable Objects give you a single-threaded instance addressed by ID - the right primitive when “last write wins in KV” is not enough.
Start with the Durable Counter experiment, then graduate to richer DO apps in the catalog.
Cloudflare products in this pattern
| Product | Role | Docs |
|---|---|---|
| Durable Objects | Per-ID strongly consistent compute + storage | Product home |
| DO storage API | Key-value transactional storage | Storage |
| SQLite in Durable Objects | Relational state per object | SQLite |
| DO alarms | Deferred work tied to an object | Alarms |
| Workers | Stub creation + HTTP/WebSocket entry | Runtime |
| WebSockets | Realtime fanout via DO | WebSockets |
| Queues | Offload heavy work from the DO | Async jobs |
When to reach for Durable Objects
Use a DO when you need one logical entity with serialized access:
- Per-user or per-room state
- Coordinating multiple clients over WebSockets
- Alarms / deferred work tied to that entity
- SQLite storage colocated with the object (DO SQLite Notes)
Prefer KV, D1, or R2 when entities are independent and eventual consistency is fine. Prefer Queues / Workflows when the problem is async fan-out rather than a sticky entity.
Official overview: What are Durable Objects?
Mental model
Client → Worker stub → idFromName("room-42") → Durable Object instanceAll requests for the same ID hit the same instance. That is the feature: no distributed lock service for the common case.
Read:
The counter teaches:
- Exporting a DO class from the Worker
- Binding it in
wrangler.json - Getting a stub by name/id
- Reading/writing storage transactionally via the storage API
From counter to real products
| Pattern | Experiment | Cloudflare docs angle |
|---|---|---|
| Increment / read / reset | Durable Counter | Storage API |
| Chat + optional Workers AI | Chat Agent | DO + Workers AI |
| Notes in DO SQLite | DO SQLite Notes | SQLite storage |
| WebSocket echo | WebSocket Echo | WebSockets |
| Alarms / scheduled DO work | DO Alarm Scheduler | Alarms |
| Queue jobs + DO view | Queue Job Visualizer | DO + Queues |
Copy one pattern at a time. A chat app that also invents a novel storage layer on day one is how experiments become unmaintainable.
Design tips from the product docs
- Name IDs carefully -
idFromNameis a permanent partition key for that name (ID docs) - Keep handlers small - do heavy AI/fetch work in the Worker or a Queue; coordinate in the DO
- Use storage transactions for multi-key updates (storage API)
- Alarms for deferred cleanup or digests instead of external cron when state-local (alarms)
- Hibernation - understand WebSocket hibernation if you build long-lived realtime (WebSockets + hibernation)
Anti-patterns
- One global DO for all users (hotspot)
- Storing large blobs in DO storage instead of R2
- Running multi-minute AI jobs inside the DO request path
- Using DOs when a simple KV counter with lossy increments would do
Deploy the counter first
- Read Durable Objects get started
- Open Durable Counter
- Deploy and call increment a few times
- Confirm consistent reads for the same object id
- Open Chat Agent or DO SQLite Notes when you need a richer shape
You learn DO addressing and storage with the smallest useful API, then compose realtime or SQLite without rewriting the foundation - Workers as the vehicle, Durable Objects as the product capability.
Production checklist
- Design ID scheme first (
idFromNameis forever for that name). - Keep DO handlers short; offload heavy work to Queues.
- Use transactions for multi-key updates (storage API).
- Prefer SQLite in DOs when you need relational queries per entity.
- Load-test hot IDs - one global DO is an anti-pattern.
- Plan alarms for cleanup (alarms).
FAQ
DO vs D1?
D1 is shared SQL across requests. A Durable Object is a sticky single-threaded entity. Use D1 for catalogs; DOs for rooms, sessions, and locks.
Can I put blobs in DO storage?
Small state yes; large files belong in R2.
Related experiments
These deployable references implement the patterns in this article. Open the docs for API details, then Deploy or clone the source.
- Durable Counter - Canonical Durable Objects counter - globally consistent increment/decrement with strong consistency at the edge
- Chat Agent - Durable AI chat agent with SQLite-backed Durable Objects and optional Workers AI - stateful conversational agent on Workers
- DO SQLite Notes - Per-user notes in SQLite-backed Durable Objects via sql.exec - structured storage pattern for stateful Workers
- WebSocket Echo - WebSocket echo server on Cloudflare Workers - bidirectional realtime messaging reference implementation
- DO Alarm Scheduler - Schedule one-off reminders with the Durable Object Alarm API and poll fired status - deferred edge jobs without Cron
- Queue Job Visualizer - Cloudflare Queues producer/consumer with KV-backed job status and simulated retries - visual job queue reference