Files
2026-01-30 03:04:10 +00:00
..
2026-01-30 03:04:10 +00:00
2026-01-30 03:04:10 +00:00
2026-01-30 03:04:10 +00:00
2026-01-30 03:04:10 +00:00
2026-01-30 03:04:10 +00:00

Cloudflare Durable Objects

Expert guidance for building stateful applications with Cloudflare Durable Objects.

Reading Order

  1. First time? Read this overview + Quick Start
  2. Setting up? See Configuration
  3. Building features? Use decision trees below → Patterns
  4. Debugging issues? Check Gotchas
  5. Deep dive? API and DO Storage

Overview

Durable Objects combine compute with storage in globally-unique, strongly-consistent packages:

  • Globally unique instances: Each DO has unique ID for multi-client coordination
  • Co-located storage: Fast, strongly-consistent storage with compute
  • Automatic placement: Objects spawn near first request location
  • Stateful serverless: In-memory state + persistent storage
  • Single-threaded: Serial request processing (no race conditions)

Rules of Durable Objects

Critical rules preventing most production issues:

  1. One alarm per DO - Schedule multiple events via queue pattern
  2. ~1K req/s per DO max - Shard for higher throughput
  3. Constructor runs every wake - Keep initialization light; use lazy loading
  4. Hibernation clears memory - In-memory state lost; persist critical data
  5. Use ctx.waitUntil() for cleanup - Ensures completion after response sent
  6. No setTimeout for persistence - Use setAlarm() for reliable scheduling

Core Concepts

Class Structure

All DOs extend DurableObject base class with constructor receiving DurableObjectState (storage, WebSockets, alarms) and Env (bindings).

Lifecycle States

[Not Created] → [Active] ⇄ [Hibernated] → [Evicted]
                   ↓
              [Destroyed]
  • Not Created: DO ID exists but instance never spawned
  • Active: Processing requests, in-memory state valid, billed per GB-hour
  • Hibernated: WebSocket connections open but zero compute, zero cost
  • Evicted: Removed from memory; next request triggers cold start
  • Destroyed: Data deleted via migration or manual deletion

Accessing from Workers

Workers use bindings to get stubs, then call RPC methods directly (recommended) or use fetch handler (legacy).

RPC vs fetch() decision:

├─ New project + compat ≥2024-04-03 → RPC (type-safe, simpler)
├─ Need HTTP semantics (headers, status) → fetch()
├─ Proxying requests to DO → fetch()
└─ Legacy compatibility → fetch()

See Patterns: RPC vs fetch() for examples.

ID Generation

  • idFromName(): Deterministic, named coordination (rate limiting, locks)
  • newUniqueId(): Random IDs for sharding high-throughput workloads
  • idFromString(): Derive from existing IDs
  • Jurisdiction option: Data locality compliance

Storage Options

Which storage API?

├─ Structured data, relations, transactions → SQLite (recommended)
├─ Simple KV on SQLite DO → ctx.storage.kv (sync KV)
└─ Legacy KV-only DO → ctx.storage (async KV)
  • SQLite (recommended): Structured data, transactions, 10GB/DO
  • Synchronous KV API: Simple key-value on SQLite objects
  • Asynchronous KV API: Legacy/advanced use cases

See DO Storage for deep dive.

Special Features

  • Alarms: Schedule future execution per-DO (1 per DO - use queue pattern for multiple)
  • WebSocket Hibernation: Zero-cost idle connections (memory cleared on hibernation)
  • Point-in-Time Recovery: Restore to any point in 30 days (SQLite only)

Quick Start

import { DurableObject } from "cloudflare:workers";

export class Counter extends DurableObject<Env> {
  async increment(): Promise<number> {
    const result = this.ctx.storage.sql.exec(
      `INSERT INTO counters (id, value) VALUES (1, 1)
       ON CONFLICT(id) DO UPDATE SET value = value + 1
       RETURNING value`
    ).one();
    return result.value;
  }
}

// Worker access
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const id = env.COUNTER.idFromName("global");
    const stub = env.COUNTER.get(id);
    const count = await stub.increment();
    return new Response(`Count: ${count}`);
  }
};

Decision Trees

What do you need?

├─ Coordinate requests (rate limit, lock, session)
│   → idFromName(identifier) → [Patterns: Rate Limiting/Locks](./patterns.md)
│
├─ High throughput (>1K req/s)
│   → Sharding with newUniqueId() or hash → [Patterns: Sharding](./patterns.md)
│
├─ Real-time updates (WebSocket, chat, collab)
│   → WebSocket hibernation + room pattern → [Patterns: Real-time](./patterns.md)
│
├─ Background work (cleanup, notifications, scheduled tasks)
│   → Alarms + queue pattern (1 alarm/DO) → [Patterns: Multiple Events](./patterns.md)
│
└─ User sessions with expiration
    → Session pattern + alarm cleanup → [Patterns: Session Management](./patterns.md)

Which access pattern?

├─ New project + typed methods → RPC (compat ≥2024-04-03)
├─ Need HTTP semantics → fetch()
├─ Proxying to DO → fetch()
└─ Legacy compat → fetch()

See Patterns: RPC vs fetch() for examples.

Which storage?

├─ Structured data, SQL queries, transactions → SQLite (recommended)
├─ Simple KV on SQLite DO → ctx.storage.kv (sync API)
└─ Legacy KV-only DO → ctx.storage (async API)

See DO Storage for complete guide.

Essential Commands

npx wrangler dev              # Local dev with DOs
npx wrangler dev --remote     # Test against prod DOs
npx wrangler deploy           # Deploy + auto-apply migrations

Resources

Docs: https://developers.cloudflare.com/durable-objects/
API Reference: https://developers.cloudflare.com/durable-objects/api/
Examples: https://developers.cloudflare.com/durable-objects/examples/

In This Reference

  • Configuration - wrangler.jsonc setup, migrations, bindings, environments
  • API - Class structure, ctx methods, alarms, WebSocket hibernation
  • Patterns - Sharding, rate limiting, locks, real-time, sessions
  • Gotchas - Limits, hibernation caveats, common errors

See Also

  • DO Storage - SQLite, KV, transactions (detailed storage guide)
  • Workers - Core Workers runtime features
  • WebSockets - WebSocket APIs and patterns