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 );
Use transactions for atomic updates
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
});
Use hibernation for many connections
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.