Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/cloudflare/workerd/llms.txt

Use this file to discover all available pages before exploring further.

Durable Objects provide strongly consistent, coordinated state for your applications. Each Durable Object has unique, transactional storage and can coordinate between multiple clients in real-time.

Overview

Durable Objects provide:
  • Strong consistency guarantees
  • Transactional storage with the Storage API
  • Persistent state across requests
  • Real-time coordination via WebSockets
  • Alarms for scheduled execution
  • Optional hibernation for cost efficiency
Implementation: src/workerd/api/actor-state.h and actor-state.c++

Defining a Durable Object

Create a Durable Object class:
export class Counter {
  constructor(state, env) {
    this.state = state;
    this.env = env;
  }

  async fetch(request) {
    // Get current count
    let count = (await this.state.storage.get('count')) || 0;
    
    // Increment
    count++;
    
    // Store
    await this.state.storage.put('count', count);
    
    return new Response(count.toString());
  }
}

Accessing Durable Objects

Access Durable Objects through namespace bindings:
export default {
  async fetch(request, env) {
    // Get a Durable Object ID
    const id = env.COUNTER.idFromName('global');
    
    // Get the Durable Object stub
    const stub = env.COUNTER.get(id);
    
    // Call the Durable Object
    const response = await stub.fetch(request);
    
    return response;
  }
};

Durable Object IDs

ID from name

Derive a deterministic ID from a name:
// Same name always produces the same ID
const id = env.ROOM.idFromName('room-123');

ID from string

Parse an ID from its string representation:
const idString = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef';
const id = env.ROOM.idFromString(idString);

New unique ID

Generate a new unique ID:
// Create a new unguessable ID
const id = env.ROOM.newUniqueId();
const idString = id.toString();

// Store or share this ID string
return new Response(idString);
Source: src/workerd/api/samples/durable-objects-chat/chat.js:147

Storage API

The storage API provides transactional key-value storage:

Get

Retrieve values:
// Get single value
const value = await this.state.storage.get('key');

// Get multiple values
const values = await this.state.storage.get(['key1', 'key2', 'key3']);
console.log(values); // Map { 'key1' => value1, 'key2' => value2, ... }
Source: src/workerd/api/actor-state.h:54

Put

Store values:
// Put single value
await this.state.storage.put('key', 'value');

// Put multiple values
await this.state.storage.put({
  'key1': 'value1',
  'key2': 'value2',
  'key3': 'value3'
});

Delete

Remove values:
// Delete single key
await this.state.storage.delete('key');

// Delete multiple keys
await this.state.storage.delete(['key1', 'key2', 'key3']);

List

List stored keys:
// List all keys
const entries = await this.state.storage.list();
for (const [key, value] of entries) {
  console.log(key, value);
}

// List with options
const entries = await this.state.storage.list({
  start: 'user:',
  end: 'user;',
  prefix: 'user:',
  reverse: false,
  limit: 100
});
Source: src/workerd/api/actor-state.h:67

Transactions

Perform atomic operations:
class BankAccount {
  async transfer(from, to, amount) {
    await this.state.storage.transaction(async (txn) => {
      // Get balances
      const fromBalance = (await txn.get(from)) || 0;
      const toBalance = (await txn.get(to)) || 0;
      
      // Validate
      if (fromBalance < amount) {
        throw new Error('Insufficient funds');
      }
      
      // Update balances
      await txn.put(from, fromBalance - amount);
      await txn.put(to, toBalance + amount);
      
      // Transaction commits automatically if no error is thrown
    });
  }
}

Alarms

Schedule future execution:
export class Task {
  constructor(state, env) {
    this.state = state;
  }

  async fetch(request) {
    // Schedule alarm for 1 hour from now
    const now = Date.now();
    await this.state.storage.setAlarm(now + 3600000);
    
    return new Response('Alarm set');
  }

  async alarm() {
    // Called when alarm fires
    console.log('Alarm triggered!');
    
    // Perform scheduled task
    await this.performTask();
  }

  async performTask() {
    // Do work...
  }
}

Get alarm

Check current alarm time:
const alarmTime = await this.state.storage.getAlarm();
if (alarmTime !== null) {
  console.log('Alarm scheduled for:', new Date(alarmTime));
}
Source: src/workerd/api/actor-state.h:65

Delete alarm

Cancel a scheduled alarm:
await this.state.storage.deleteAlarm();

WebSocket coordination

Coordinate real-time communication:
export class ChatRoom {
  constructor(state, env) {
    this.state = state;
    this.sessions = [];
  }

  async fetch(request) {
    if (request.headers.get('Upgrade') !== 'websocket') {
      return new Response('Expected WebSocket', { status: 400 });
    }

    const pair = new WebSocketPair();
    const [client, server] = pair;

    // Accept the WebSocket
    server.accept();

    // Add to sessions
    this.sessions.push(server);

    // Handle messages
    server.addEventListener('message', event => {
      const message = event.data;
      
      // Broadcast to all sessions
      for (const session of this.sessions) {
        try {
          session.send(message);
        } catch (err) {
          // Session closed
        }
      }
    });

    // Handle close
    server.addEventListener('close', () => {
      this.sessions = this.sessions.filter(s => s !== server);
    });

    return new Response(null, {
      status: 101,
      webSocket: client
    });
  }
}
Source: src/workerd/api/samples/durable-objects-chat/chat.js:213

Hibernatable WebSockets

Reduce memory usage with hibernation:
export class HibernatableRoom {
  constructor(state, env) {
    this.state = state;
    
    // Enable hibernation
    this.state.setHibernatableWebSocketEventTimeout(30_000);
  }

  async fetch(request) {
    const pair = new WebSocketPair();
    const [client, server] = pair;

    // Accept with hibernation support
    this.state.acceptWebSocket(server);

    return new Response(null, {
      status: 101,
      webSocket: client
    });
  }

  // Called when a message is received
  async webSocketMessage(ws, message) {
    // Durable Object wakes up if hibernating
    ws.send(`Echo: ${message}`);
  }

  // Called when connection closes
  async webSocketClose(ws, code, reason, wasClean) {
    console.log('WebSocket closed:', code, reason);
  }

  // Called on error
  async webSocketError(ws, error) {
    console.error('WebSocket error:', error);
  }
}

Patterns

Singleton coordination

Use a single Durable Object for global state:
class GlobalCounter {
  constructor(state) {
    this.state = state;
  }

  async increment() {
    const count = (await this.state.storage.get('count')) || 0;
    const newCount = count + 1;
    await this.state.storage.put('count', newCount);
    return newCount;
  }
}

// Access the singleton
const id = env.COUNTER.idFromName('global');
const counter = env.COUNTER.get(id);

Per-user state

Store state for each user:
class UserSession {
  constructor(state, env) {
    this.state = state;
  }

  async fetch(request) {
    const session = await this.state.storage.get('session') || {};
    
    // Update last seen
    session.lastSeen = Date.now();
    await this.state.storage.put('session', session);
    
    return new Response(JSON.stringify(session));
  }
}

// Access per user
const id = env.SESSION.idFromName(`user:${userId}`);
const session = env.SESSION.get(id);

Rate limiting

Implement distributed rate limiting:
class RateLimiter {
  constructor(state) {
    this.state = state;
    this.nextAllowedTime = 0;
  }

  async fetch(request) {
    const now = Date.now() / 1000;
    this.nextAllowedTime = Math.max(now, this.nextAllowedTime);

    if (request.method === 'POST') {
      // Allow one request per 5 seconds
      this.nextAllowedTime += 5;
    }

    // Calculate cooldown with 20 second grace period
    const cooldown = Math.max(0, this.nextAllowedTime - now - 20);
    return new Response(cooldown.toString());
  }
}
Source: src/workerd/api/samples/durable-objects-chat/chat.js:451

Best practices

Durable Objects are designed for coordination, not bulk storage:
// Good: small coordinated state
await this.state.storage.put('counter', count);

// Bad: storing large files
await this.state.storage.put('video', hugeBuffer);
Ensure consistency with transactions:
await this.state.storage.transaction(async (txn) => {
  const val1 = await txn.get('key1');
  const val2 = await txn.get('key2');
  await txn.put('key1', val1 + 1);
  await txn.put('key2', val2 - 1);
});
Clean up resources when connections close:
server.addEventListener('close', () => {
  this.sessions = this.sessions.filter(s => s !== server);
  // Clean up other resources
});
Reduce memory usage with hibernatable WebSockets:
this.state.acceptWebSocket(server);
// Connection can hibernate when idle

Storage options

Control caching and consistency:
// Allow concurrent reads (eventual consistency)
const value = await this.state.storage.get('key', {
  allowConcurrency: true
});

// Bypass cache
const value = await this.state.storage.get('key', {
  noCache: true
});

// Allow unconfirmed writes (performance)
await this.state.storage.put('key', value, {
  allowUnconfirmed: true
});
Source: src/workerd/api/actor-state.h:42

Implementation details

Durable Objects are implemented in:
  • src/workerd/api/actor-state.h / .c++ - Storage API (1800+ lines)
  • src/workerd/api/actor.h / .c++ - Durable Object infrastructure
  • src/workerd/io/actor-cache.h / .c++ - LRU cache layer over storage
  • src/workerd/io/actor-sqlite.h / .c++ - SQLite-backed storage
The storage API provides transactional operations over an actor cache that sits in front of SQLite storage.

Build docs developers (and LLMs) love