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

6.0 KiB

Durable Objects API

Class Structure

import { DurableObject } from "cloudflare:workers";

export class MyDO extends DurableObject<Env> {
  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
    // Runs on EVERY wake - keep light!
  }
  
  // RPC methods (called directly from worker)
  async myMethod(arg: string): Promise<string> { return arg; }
  
  // fetch handler (legacy/HTTP semantics)
  async fetch(req: Request): Promise<Response> { /* ... */ }
  
  // Lifecycle handlers
  async alarm() { /* alarm fired */ }
  async webSocketMessage(ws: WebSocket, msg: string | ArrayBuffer) { /* ... */ }
  async webSocketClose(ws: WebSocket, code: number, reason: string, wasClean: boolean) { /* ... */ }
  async webSocketError(ws: WebSocket, error: unknown) { /* ... */ }
}

DurableObjectState Context Methods

Concurrency Control

// Complete work after response sent (e.g., cleanup, logging)
this.ctx.waitUntil(promise: Promise<any>): void

// Critical section - blocks all other requests until complete
await this.ctx.blockConcurrencyWhile(async () => {
  // No other requests processed during this block
  // Use for initialization or critical operations
})

When to use:

  • waitUntil(): Background cleanup, logging, non-critical work after response
  • blockConcurrencyWhile(): First-time init, schema migration, critical state setup

Lifecycle

this.ctx.id              // DurableObjectId of this instance
this.ctx.abort()         // Force eviction (use after PITR restore to reload state)

Storage Access

this.ctx.storage.sql     // SQLite API (recommended)
this.ctx.storage.kv      // Sync KV API (SQLite DOs only)
this.ctx.storage         // Async KV API (legacy/KV-only DOs)

See DO Storage for complete storage API reference.

WebSocket Management

this.ctx.acceptWebSocket(ws: WebSocket, tags?: string[])  // Enable hibernation
this.ctx.getWebSockets(tag?: string): WebSocket[]         // Get by tag or all
this.ctx.getTags(ws: WebSocket): string[]                 // Get tags for connection

Alarms

await this.ctx.storage.setAlarm(timestamp: number | Date)  // Schedule (overwrites existing)
await this.ctx.storage.getAlarm(): number | null           // Get next alarm time
await this.ctx.storage.deleteAlarm(): void                 // Cancel alarm

Limit: 1 alarm per DO. Use queue pattern for multiple events (see Patterns).

Storage APIs

For detailed storage documentation including SQLite queries, KV operations, transactions, and Point-in-Time Recovery, see DO Storage.

Quick reference:

// SQLite (recommended)
this.ctx.storage.sql.exec("SELECT * FROM users WHERE id = ?", userId).one()

// Sync KV (SQLite DOs only)
this.ctx.storage.kv.get("key")

// Async KV (legacy)
await this.ctx.storage.get("key")

Alarms

Schedule future work that survives eviction:

// Set alarm (overwrites any existing alarm)
await this.ctx.storage.setAlarm(Date.now() + 3600000)  // 1 hour from now
await this.ctx.storage.setAlarm(new Date("2026-02-01"))  // Absolute time

// Check next alarm
const nextRun = await this.ctx.storage.getAlarm()  // null if none

// Cancel alarm
await this.ctx.storage.deleteAlarm()

// Handler called when alarm fires
async alarm() {
  // Runs once alarm triggers
  // DO wakes from hibernation if needed
  // Use for cleanup, notifications, scheduled tasks
}

Limitations:

  • 1 alarm per DO maximum
  • Overwrites previous alarm when set
  • Use queue pattern for multiple scheduled events (see Patterns)

Reliability:

  • Alarms survive DO eviction/restart
  • Cloudflare retries failed alarms automatically
  • Not guaranteed exactly-once (handle idempotently)

WebSocket Hibernation

Hibernation allows DOs with open WebSocket connections to consume zero compute/memory until message arrives.

async fetch(req: Request): Promise<Response> {
  const [client, server] = Object.values(new WebSocketPair());
  this.ctx.acceptWebSocket(server, ["room:123"]);  // Tags for filtering
  server.serializeAttachment({ userId: "abc" });    // Persisted metadata
  return new Response(null, { status: 101, webSocket: client });
}

// Called when message arrives (DO wakes from hibernation)
async webSocketMessage(ws: WebSocket, msg: string | ArrayBuffer) {
  const data = ws.deserializeAttachment();          // Retrieve metadata
  for (const c of this.ctx.getWebSockets("room:123")) c.send(msg);
}

// Called on close (optional handler)
async webSocketClose(ws: WebSocket, code: number, reason: string, wasClean: boolean) {
  // Cleanup logic, remove from lists, etc.
}

// Called on error (optional handler)
async webSocketError(ws: WebSocket, error: unknown) {
  console.error("WebSocket error:", error);
  // Handle error, close connection, etc.
}

Key concepts:

  • Auto-hibernation: DO hibernates when no active requests/alarms
  • Zero cost: Hibernated DOs incur no charges while preserving connections
  • Memory cleared: All in-memory state lost on hibernation
  • Attachment persistence: Use serializeAttachment() for per-connection metadata that survives hibernation
  • Tags for filtering: Group connections by room/channel/user for targeted broadcasts

Handler lifecycle:

  • webSocketMessage: DO wakes, processes message, may hibernate after
  • webSocketClose: Called when client closes (optional - implement for cleanup)
  • webSocketError: Called on connection error (optional - implement for error handling)

Metadata persistence:

// Store connection metadata (survives hibernation)
ws.serializeAttachment({ userId: "abc", room: "lobby" })

// Retrieve after hibernation
const { userId, room } = ws.deserializeAttachment()

See Also