Date: 2026-07-15 Sources: Official Cloudflare Durable Objects documentation (fetched live)
Durable Objects are a special kind of Cloudflare Worker that uniquely combines compute with storage in a single, globally-unique, single-threaded instance. They enable stateful serverless applications — real-time chat, multiplayer games, collaborative editing, booking systems, and distributed coordination — without requiring developers to build serialization, locking, or coordination primitives themselves.
Each Durable Object is implicitly created on first access, provisioned geographically close to the first requester, and can scale to millions of instances across Cloudflare's global network. Storage is colocated with compute (SQLite-backed, up to 10 GB per object), providing strongly consistent, transactional, zero-latency access. The Actor programming model underpins the design: each object receives messages (HTTP/RPC requests), executes logic in its own single-threaded context, and sends messages back out.
Key differentiators from plain Workers:
- Globally-unique identity — addressable from anywhere
- Colocated durable storage — strongly consistent, no network hop
- Single-threaded concurrency — no race conditions by construction
- Elastic horizontal scaling — millions of objects, no capacity planning
A Durable Object is a special kind of Cloudflare Worker which uniquely combines compute with storage [1]. Like a Worker, it is automatically provisioned geographically close to where it is first requested, starts up quickly when needed, and shuts down when idle. Unlike regular Workers:
- Each Durable Object has a globally-unique name, allowing requests to be sent to a specific object from anywhere in the world [1].
- Each Durable Object has durable storage attached, which lives together with the object, is strongly consistent, and is fast to access [1].
Durable Objects are single-threaded and cooperatively multi-tasked, just like code running in a web browser [1]. This eliminates most concurrency pitfalls (race conditions, deadlocks) that arise from shared-memory concurrency.
The runtime uses input gates and output gates to prevent data races:
- Input gates block new events (incoming requests, fetch responses) while synchronous JavaScript execution is in progress. Awaiting async operations like
fetch()opens the input gate, allowing other requests to interleave. Storage operations provide special protection — while they execute, no other requests can interleave [2]. - Output gates hold outgoing network messages (responses, fetch requests) until pending storage writes complete. This ensures clients never see confirmation of data that has not been persisted [2].
- Write coalescing: Multiple storage writes without intervening
awaitcalls are automatically batched into a single atomic implicit transaction [2].
Each Durable Object instance can be seen as an Actor instance, receiving messages (incoming HTTP/RPC requests), executing some logic in its own single-threaded context using its attached durable storage or in-memory state, and finally sending messages to the outside world (outgoing HTTP/RPC requests or responses), even to another Durable Object instance [1].
This is analogous to Actor models in Erlang, Elixir, Akka, or Microsoft Orleans [1].
Durable Objects support Workers RPC, which allows applications to use JavaScript-native methods and objects to communicate between Workers and Durable Objects [1]. Using RPC makes application development easier and more efficient than HTTP fetch.
Projects with a compatibility date of 2024-04-03 or later should use RPC methods [2].
A Durable Object can be in one of five states [3]:
| State | Description |
|---|---|
| Active, in-memory | Runs, in memory, handles incoming requests |
| Idle, in-memory non-hibernateable | Waits for next request/event, but does not satisfy hibernation criteria |
| Idle, in-memory hibernateable | Waits for next request/event, satisfies hibernation criteria. Runtime decides when to hibernate (currently after 10 seconds of inactivity) |
| Hibernated | Removed from memory. Hibernated WebSocket connections stay connected |
| Inactive | Completely removed from host process, may need cold start. Initial state of all Durable Objects |
Hibernation can only occur if all of the following are true [3]:
- No
setTimeout/setIntervalscheduled callbacks are set - No I/O operation or promise passed to
this.ctx.waitUntil()remains unfinished, and no outbound connection remains open - No WebSocket standard API is used
- No request/event is still being processed
After 10 seconds of no incoming request or event (with all conditions satisfied), the object transitions to hibernated. In-memory state is discarded at this point [3].
While in the idle, in-memory, non-hibernateable state, after 70–140 seconds of inactivity, the Durable Object is evicted entirely from memory and potentially from the Cloudflare host, transitioning to inactive [3].
Pending I/O and open outbound connections prevent eviction. Each operation prevents eviction until it completes or for up to 15 minutes from when it starts [3]. While an operation prevents eviction, the Durable Object continues to incur duration charges.
Durable Objects will occasionally shut down and restart (running the constructor again) due to:
- New Worker deployments with code updates
- Lack of requests following state transitions
- Cloudflare updates to the Workers runtime
- Runtime decisions on where to host objects [3]
No shutdown hooks are provided. The recommended pattern is to write state incrementally to storage as you go, rather than relying on a final flush [3].
A Durable Object incurs charges only when it is actively running in-memory or idle in-memory and non-hibernateable [3]. Hibernated objects do not incur duration charges.
Cloudflare recommends all new Durable Object namespaces use the SQLite storage backend [4]. It provides:
- A familiar SQL API for relational queries, indexes, and transactions
- Point-in-Time Recovery (PITR) — restore to any point in the past 30 days
- Both synchronous and asynchronous KV API methods
- Better performance than the legacy key-value backend
Configure in Wrangler:
{
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["ChatRoom"] }
]
}Accessed via ctx.storage.sql.exec(query, ...bindings) [4]:
- Supports
?placeholders for parameter bindings - Multiple SQL statements can be executed in one call
- Returns a
SqlStorageCursor(Iterable/Iterator) with.toArray(),.one(),.raw()methods - Supports FTS5 (full-text search), JSON extension, and Math functions
- Cannot execute
BEGIN TRANSACTIONorSAVEPOINT— usectx.storage.transaction()ortransactionSync()instead
- Synchronous:
ctx.storage.kv.get/put/delete/list— data stored in a hidden__cf_kvSQLite table - Asynchronous:
ctx.storage.get/put/delete/list— supports multi-key operations (up to 128 keys),allowConcurrency,noCacheoptions
- Automatic write coalescing: Multiple
put()/delete()calls without interveningawaitare submitted atomically [4] transactionSync(callback): Synchronous transaction for SQL operations (SQLite-backed only)transaction(closure): Asynchronous transaction for KV operationssync(): Flushes pending writes to disk
SQLite-backed Durable Objects support restoring the entire database to any point in the past 30 days [4]:
getCurrentBookmark()— get current point in timegetBookmarkForTime(timestamp)— get bookmark for a specific timeonNextSessionRestoreBookmark(bookmark)— schedule restore on next restart
| Feature | Limit |
|---|---|
| Storage per Durable Object | 10 GB (Paid) / 1 GB (Free) |
| Key + Value size | 2 MB combined |
| Max columns per table | 100 |
| Max rows per table | Unlimited (within per-object storage) |
| Max string/BLOB/row size | 2 MB |
| Max SQL statement length | 100 KB |
| Max bound parameters per query | 100 |
[5]
Workers call Durable Object methods directly via stubs:
const stub = env.MY_OBJECT.getByName("foo");
const result = await stub.someMethod(arg);Each RPC method call is billed as one request. Calls on returned RpcTarget stubs are part of the same session [6].
Durable Objects can also be invoked via fetch() to their URL, using the standard fetch(request, env) handler.
Durable Objects support the WebSocket Standard API and the WebSocket Hibernation API [1]:
- Standard:
webSocketAccept(),webSocketMessage(),webSocketClose() - Hibernation:
webSocketAcceptHibernatable()— reduces costs by not incurring billing during inactivity - WebSocket message size limit: 32 MiB (received)
- A 20:1 billing ratio applies to incoming WebSocket messages [6]
Durable Objects can call other Durable Objects via RPC stubs. The parent-child pattern is recommended for hierarchical data (workspaces → projects, game servers → matches) [2].
6 per request (same as Workers) [5].
Durable Objects provide an Alarms API to schedule future wake-ups [1][4]:
setAlarm(scheduledTime)— schedule a wake-up (millisecond granularity, may be delayed up to 1 minute)getAlarm()— retrieve current alarm timedeleteAlarm()— cancel a pending alarmalarm()handler method — invoked when the alarm fires- Guaranteed at-least-once execution
- Each
setAlarm()is billed as a single row written - Alarm handler wall time limit: 15 minutes [5]
Use cases: periodic work, batch processing, TTL expiration, subscription renewals, game timeouts.
- Coordination — multiple clients interacting with shared state (chat, multiplayer, collaborative docs)
- Strong consistency — operations must be serialized (inventory, booking, turn-based games)
- Per-entity storage — each user/tenant/resource needs isolated data (multi-tenant SaaS)
- Persistent connections — long-lived WebSockets (real-time notifications, live updates)
- Scheduled work per entity — per-entity timers (subscription renewals, game timeouts)
- Stateless request handling (use plain Workers)
- Maximum global distribution (Workers handle at nearest edge)
- High fan-out with independent requests
- Model around your "atom" of coordination — one DO per chat room, game session, document, user
- Use deterministic IDs —
getByName()with meaningful strings for consistent routing - Parent-child relationships — parent coordinates, children handle their own state independently
- Location hints —
get(id, { locationHint: region })for latency-sensitive apps - Initialize in constructor — use
blockConcurrencyWhile()for migrations/schema setup - Write state incrementally — no shutdown hooks; persist as you go
- Use indexes — for frequently-queried columns
- Avoid
blockConcurrencyWhile()on every request — limits throughput to ~200 req/s
| Operation type | Throughput |
|---|---|
| Simple pass-through | ~1,000 req/s |
| Moderate processing | ~500–750 req/s |
| Complex operations | ~200–500 req/s |
- Cloudflare Queues (10x speedup)
- Stream Live (live streaming)
- AI Gateway (WebSockets + auth)
- Workers Builds (CI/CD)
- Workflows (durable execution)
- D1 (global database)
- AI Gateway scaling (billions of logs)
| Constraint | Value |
|---|---|
| CPU per request | 30s default, configurable to 5 min [5] |
| Memory per object | 128 MB [6] |
| Storage per object | 10 GB (Paid) / 1 GB (Free) [5] |
| Key + Value size | 2 MB [5] |
| WebSocket message size | 32 MiB [5] |
| Simultaneous outgoing connections | 6 [5] |
| Max DO classes per account | 500 (Paid) / 100 (Free) [5] |
| Storage per account | Unlimited (Paid) / 5 GB (Free) [5] |
| Wall time (RPC/HTTP) | Unlimited while caller connected [5] |
| Wall time (alarms) | 15 minutes [5] |
| No file system access | N/A |
| No shutdown hooks | Design for incremental persistence [3] |
| Single-writer constraint | Inherent in single-threaded model [1] |
| In-memory state lost on hibernation | Must persist to storage [3] |
PRAGMA user_version not supported |
Use alternative schema versioning [2] |
Cursors across await |
No snapshot isolation guarantee [4] |
- No shutdown hooks — must write state incrementally
- In-memory state is volatile (lost on hibernation/eviction)
- Non-storage I/O (
fetch(), R2) allows request interleaving — use optimistic locking blockConcurrencyWhile()across I/O is an anti-pattern- Code updates cause eventual-consistency window where new Worker version may call old DO version
- Storage billing for SQLite starts January 2026
| Dimension | Free Plan | Paid Plan |
|---|---|---|
| Requests | 100,000/day | 1M/month included, +$0.15/M |
| Duration | 13,000 GB-s/day | 400,000 GB-s/month included, +$12.50/M GB-s |
- Duration is billed in wall-clock time while active and not hibernateable
- Billed for 128 MB memory allocation regardless of actual usage
- WebSocket messages: 20:1 ratio (100 messages = 5 billable requests)
- RPC: each method call = 1 request; calls on returned stubs are free
| Dimension | Free Plan | Paid Plan |
|---|---|---|
| Rows read | 5M/day | 25B/month included, +$0.001/M |
| Rows written | 100K/day | 50M/month included, +$1.00/M |
| Stored data | 5 GB total | 5 GB-month included, +$0.20/GB-month |
- Storage billing starts January 7, 2026
- KV methods (get/put/delete/list) are billed as rows read/written
- Each
setAlarm()= 1 row written - Deletes count as rows written
- Simple coordination service (1.5M requests, 1M seconds active): ~$5.15/month
- 100 DOs, 50 WebSocket connections each, 8h/day: ~$142.95/month
- 100 DOs, 1 WebSocket each, 24/7: ~$419.30/month
- 100 DOs, 100 hibernatable WebSockets each, 1 msg/min: ~$20.65/month (WebSocket Hibernation saves ~95% vs non-hibernated)
WebSocket Hibernation dramatically reduces costs for real-time applications with sparse message patterns. Example 4 vs Example 2: $20.65 vs $142.95 for similar traffic [6].
| Feature | Durable Objects | Workers (stateless) | KV | D1 | R2 |
|---|---|---|---|---|---|
| Stateful | ✅ Per-object | ❌ | ✅ (shared) | ✅ (shared) | ✅ (shared) |
| Strong consistency | ✅ (per-object) | N/A | ❌ (eventual) | ✅ | ❌ (eventual) |
| Single-writer guarantee | ✅ | N/A | ❌ | ❌ | ❌ |
| Colocated storage | ✅ | ❌ | ❌ | ❌ | ❌ |
| WebSockets | ✅ | ✅ | ❌ | ❌ | ❌ |
| Alarms/scheduling | ✅ Per-object | ❌ (Cron) | ❌ | ❌ | ❌ |
| SQL queries | ✅ (embedded) | ❌ | ❌ | ✅ | ❌ |
| Global uniqueness | ✅ Per-object | ❌ | ❌ | ❌ | ❌ |
| Scale | Millions of objects | Unlimited | 100M keys | 100M rows | Unlimited |
| Best for | Coordination, real-time, per-entity state | Stateless APIs | Config, caching | Relational data | File storage |
- No infrastructure management — DOs are serverless, auto-scaled, auto-migrated
- No network hop to storage — storage is colocated (zero-latency)
- No distributed locking — single-threaded model eliminates race conditions
- No capacity planning — millions of objects scale elastically
- Trade-off: Single-writer per object (shard your data), 128 MB memory, 30s CPU limit
- SQLite storage GA: SQLite-backed Durable Objects moved from beta to general availability. New DO classes should use SQLite [1].
- Free plan availability: SQLite-backed Durable Objects are now available on the Workers Free plan [1].
- PITR API: Point-in-Time Recovery (30-day window) for SQLite-backed DOs [4].
- WebSocket Hibernation: Reduces costs by not billing during WebSocket inactivity [1].
- RPC as default: Projects with compatibility date ≥ 2024-04-03 should use RPC over fetch [2].
- Storage billing (Jan 2026): SQLite storage billing enabled January 7, 2026 [4][6].
- Data Studio (Beta): View and edit SQLite-backed DO storage data through the Cloudflare dashboard [7].
- WebGPU: GPU access from within Durable Objects in local development [7].
- Rust API: Durable Objects can be written in Rust [7].
deleteAll()deletes alarms: For compatibility date ≥ 2026-02-24,deleteAll()also deletes active alarms [4].- KV backend deprecation: Creating new namespaces with KV storage backend is no longer supported for accounts without an existing KV-backed namespace [4].
- Storage per object raising to 10 GB: The docs note "Storage per Durable Object with SQLite is currently 1 GB. This will be raised to 10 GB for general availability" [1], but the limits page already lists 10 GB for Paid [5]. Clarification on exact GA timeline needed.
- Global uniqueness guarantees under code updates: Known issue where a request may reach a new Worker version while calling a DO still running a previous version [3]. No documented mitigation beyond Gradual Deployments.
- Multi-region data residency: Location hints are suggestions, not guarantees. Jurisdictional restrictions exist but details are in a separate doc not fetched here.
- Performance under contention: The ~1,000 req/s soft limit per DO is documented, but behavior under sustained overload (queueing, backpressure) is not fully specified.
[1] Cloudflare Durable Objects — What are Durable Objects? (concepts) https://developers.cloudflare.com/durable-objects/concepts/what-are-durable-objects/
[2] Cloudflare Durable Objects — Rules of Durable Objects (best practices) https://developers.cloudflare.com/durable-objects/best-practices/rules-of-durable-objects/
[3] Cloudflare Durable Objects — Lifecycle of a Durable Object (concepts) https://developers.cloudflare.com/durable-objects/concepts/durable-object-lifecycle/
[4] Cloudflare Durable Objects — SQLite-backed Durable Object Storage (API reference) https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/
[5] Cloudflare Durable Objects — Limits (platform) https://developers.cloudflare.com/durable-objects/platform/limits/
[6] Cloudflare Durable Objects — Pricing (platform) https://developers.cloudflare.com/durable-objects/platform/pricing/
[7] Cloudflare Durable Objects — Documentation Index https://developers.cloudflare.com/durable-objects/llms.txt