jackspace/claudeskillz/cloudflare-durable-objects

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Guide to Cloudflare Durable Objects: globally unique, stateful objects for coordination, real- time communication and persistent state. Covers the DurableObject class and bindings, WebSocket Hibernation (ctx.acceptWebSocket, webSocketMessage, serializeAttachment), SQLite storage via ctx.storage.sql, alarms with storage.setAlarm and the alarm() handler, ids from idFromName, newUniqueId and getByName, RPC methods, blockConcurrencyWhile, migrations and location hints. Documents 15+ pitfalls: class not exported, missing or wrong migrations, new_sqlite_classes, constructor work and setTimeout or fetch breaking hibernation, in-memory state lost on hibernation, partial deleteAll on the KV backend, binding mismatches, state size limits and alarm retry failures. Use when building chat rooms, multiplayer games, rate limiters, session stores, queues or per-user state on Workers, or debugging DO migration, hibernation and alarm errors.

Install

npx vskill@latest install jackspace/claudeskillz/cloudflare-durable-objects

SKILL.md

Cloudflare Durable Objects

Status: Production Ready ✅ Last Updated: 2025-10-22 Dependencies: cloudflare-worker-base (recommended) Latest Versions: wrangler@4.43.0+, @cloudflare/workers-types@4.20251014.0+ Official Docs: https://developers.cloudflare.com/durable-objects/


What are Durable Objects?

Cloudflare Durable Objects are globally unique, stateful objects that provide:

  • Single-point coordination - Each Durable Object instance is globally unique across Cloudflare's network
  • Strong consistency - Transactional, serializable storage (ACID guarantees)
  • Real-time communication - WebSocket Hibernation API for thousands of connections per instance
  • Persistent state - Built-in SQLite database (up to 1GB) or key-value storage
  • Scheduled tasks - Alarms API for future task execution
  • Global distribution - Automatically routed to optimal location
  • Automatic scaling - Millions of independent instances

Use Cases:

  • Chat rooms and real-time collaboration
  • Multiplayer game servers
  • Rate limiting and session management
  • Leader election and coordination
  • WebSocket servers with hibernation
  • Stateful workflows and queues
  • Per-user or per-room logic

Quick Start (10 Minutes)

Option 1: Scaffold New DO Project
bash
npm create cloudflare@latest my-durable-app -- \
--template=cloudflare/durable-objects-template \
--ts \
--git \
--deploy false
cd my-durable-app
npm install
npm run dev

What this creates:

  • Complete Durable Objects project structure
  • TypeScript configuration
  • wrangler.jsonc with bindings and migrations
  • Example DO class implementation
  • Worker to call the DO
Option 2: Add to Existing Worker
bash
cd my-existing-worker
npm install -D @cloudflare/workers-types

Create a Durable Object class (src/counter.ts):

typescript
import { DurableObject } from 'cloudflare:workers';
export class Counter extends DurableObject {
async increment(): Promise<number> {
// Get current value from storage (default to 0)
let value: number = (await this.ctx.storage.get('value')) || 0;
// Increment
value += 1;
// Save back to storage
await this.ctx.storage.put('value', value);
return value;
}
async get(): Promise<number> {
return (await this.ctx.storage.get('value')) || 0;
}
}
// CRITICAL: Export the class
export default Counter;

Configure wrangler.jsonc:

jsonc
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2025-10-22",
// Durable Objects binding
"durable_objects": {
"bindings": [
{
"name": "COUNTER", // How you access it: env.COUNTER
"class_name": "Counter" // MUST match exported class name
}
]
},
// REQUIRED: Migration for new DO class
"migrations": [
{
"tag": "v1", // Unique migration identifier
"new_sqlite_classes": [ // Use SQLite backend (recommended)
"Counter"
]
}
]
}

Call from Worker (src/index.ts):

typescript
import { Counter } from './counter';
interface Env {
COUNTER: DurableObjectNamespace<Counter>;
}
export { Counter };
export default {
async fetch(request: Request, env: Env): Promise<Response> {
// Get Durable Object stub by name
const id = env.COUNTER.idFromName('global-counter');
const stub = env.COUNTER.get(id);
// Call RPC method on the DO
const count = await stub.increment();
return new Response(`Count: ${count}`);
},
};

Deploy:

bash
npx wrangler deploy

Durable Object Class Structure

Base Class Pattern

All Durable Objects MUST extend `DurableObject` from cloudflare:workers:

typescript
import { DurableObject } from 'cloudflare:workers';
export class MyDurableObject extends DurableObject {
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
// Optional: Initialize from storage
ctx.blockConcurrencyWhile(async () => {
// Load state before handling requests
this.someValue = await ctx.storage.get('someKey') || defaultValue;
});
}
// RPC methods (recommended)
async myMethod(): Promise<string> {
return 'Hello from DO!';
}
// Optional: HTTP fetch handler
async fetch(request: Request): Promise<Response> {
return new Response('Hello from DO fetch!');
}
}
// CRITICAL: Export the class
export default MyDurableObject;
Constructor Pattern
typescript
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env); // REQUIRED
// Access to environment bindings
this.env = env;
// this.ctx provides:
// - this.ctx.storage (storage API)
// - this.ctx.id (unique ID)
// - this.ctx.waitUntil() (background tasks)
// - this.ctx.acceptWebSocket() (WebSocket hibernation)
}

CRITICAL Rules:

  • ✅ Always call `super(ctx, env)` first
  • ✅ Keep constructor minimal - heavy work blocks hibernation wake-up
  • ✅ Use `ctx.blockConcurrencyWhile()` to initialize from storage before requests
  • ❌ Never use `setTimeout` or `setInterval` - breaks hibernation (use alarms instead)
  • ❌ Don't rely only on in-memory state with WebSockets - persist to storage
Exporting the Class
typescript
// Export as default (required for Worker to use it)
export default MyDurableObject;
// Also export as named export (for type inference in Worker)
export { MyDurableObject };

In Worker:

typescript
// Import the class for types
import { MyDurableObject } from './my-durable-object';
// Export it so Worker can instantiate it
export { MyDurableObject };
interface Env {
MY_DO: DurableObjectNamespace<MyDurableObject>;
}

State API - Persistent Storage

Durable Objects provide two storage APIs depending on the backend:

  1. SQL API (SQLite backend) - Recommended
  2. Key-Value API (KV or SQLite backend)
Enable SQLite Backend (Recommended)

In wrangler.jsonc migrations:

jsonc
{
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["MyDurableObject"] // ← Use this for SQLite
}
]
}

Why SQLite?

  • ✅ Up to 1GB storage (vs 128MB for KV backend)
  • ✅ Atomic operations (deleteAll is all-or-nothing)
  • ✅ SQL queries with transactions
  • ✅ Point-in-time recovery (PITR)
  • ✅ Synchronous KV API available too
SQL API

Access via ctx.storage.sql:

typescript
import { DurableObject } from 'cloudflare:workers';
export class MyDurableObject extends DurableObject {
sql: SqlStorage;
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
this.sql = ctx.storage.sql;
// Create table on first run
this.sql.exec(`
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
text TEXT NOT NULL,
user TEXT NOT NULL,
created_at INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_created_at ON messages(created_at);
`);
}
async addMessage(text: string, user: string): Promise<number> {
// Insert with exec (returns cursor)
const cursor = this.sql.exec(
'INSERT INTO messages (text, user, created_at) VALUES (?, ?, ?) RETURNING id',
text,
user,
Date.now()
);
const row = cursor.one<{ id: number }>();
return row.id;
}
async getMessages(limit: number = 50): Promise<any[]> {
const cursor = this.sql.exec(
'SELECT * FROM messages ORDER BY created_at DESC LIMIT ?',
limit
);
// Convert cursor to array
return cursor.toArray();
}
async deleteOldMessages(beforeTimestamp: number): Promise<void> {
this.sql.exec(
'DELETE FROM messages WHERE created_at < ?',
beforeTimestamp
);
}
}

SQL API Methods:

typescript
// Execute query (returns cursor)
const cursor = this.sql.exec('SELECT * FROM table WHERE id = ?', id);
// Get single row
const row = cursor.one<RowType>();
// Get first row or null
const row = cursor.one<RowType>({ allowNone: true });
// Get all rows as array
const rows = cursor.toArray<RowType>();
// Iterate cursor
for (const row of cursor) {
// Process row
}
// Transactions (synchronous)
this.ctx.storage.transactionSync(() => {
this.sql.exec('INSERT INTO table1 ...');
this.sql.exec('UPDATE table2 ...');
// All or nothing
});

CRITICAL SQL Rules:

  • ✅ Always use parameterized queries with ? placeholders
  • ✅ Create indexes for frequently queried columns
  • ✅ Use transactions for multi-statement operations
  • ❌ Don't access the hidden __cf_kv table (used internally for KV API)
  • ❌ Don't enable SQLite on existing deployed KV-backed DOs (not supported)
Key-Value API

Available on both SQLite and KV backends via ctx.storage:

typescript
import { DurableObject } from 'cloudflare:workers';
export class MyDurableObject extends DurableObject {
async increment(): Promise<number> {
// Get value
let count = await this.ctx.storage.get<number>('count') || 0;
// Increment
count += 1;
// Put value back
await this.ctx.storage.put('count', count);
return count;
}
async batchOperations(): Promise<void> {
// Get multiple keys
const map = await this.ctx.storage.get<number>(['key1', 'key2', 'key3']);
// Put multiple keys
await this.ctx.storage.put({
key1: 'value1',
key2: 'value2',
key3: 'value3',
});
// Delete key
await this.ctx.storage.delete('key1');
// Delete multiple keys
await this.ctx.storage.delete(['key2', 'key3']);
}
async listKeys(): Promise<string[]> {
// List all keys
const map = await this.ctx.storage.list();
return Array.from(map.keys());
// List with prefix
const mapWithPrefix = await this.ctx.storage.list({
prefix: 'user:',
limit: 100,
});
}
async deleteAllStorage(): Promise<void> {
// Delete alarm first (if set)
await this.ctx.storage.deleteAlarm();
// Delete all storage (DO will cease to exist after shutdown)
await this.ctx.storage.deleteAll();
}
}

KV API Methods:

typescript
// Get single value
const value = await this.ctx.storage.get<T>('key');
// Get multiple values (returns Map)
const map = await this.ctx.storage.get<T>(['key1', 'key2']);
// Put single value
await this.ctx.storage.put('key', value);
// Put multiple values
await this.ctx.storage.put({ key1: value1, key2: value2 });
// Delete single key
await this.ctx.storage.delete('key');
// Delete multiple keys
await this.ctx.storage.delete(['key1', 'key2']);
// List keys
const map = await this.ctx.storage.list<T>({
prefix: 'user:',
limit: 100,
reverse: false
});
// Delete all (atomic on SQLite, may be partial on KV backend)
await this.ctx.storage.deleteAll();
// Transactions (async)
await this.ctx.storage.transaction(async (txn) => {
await txn.put('key1', value1);
await txn.put('key2', value2);
// All or nothing
});

Storage Limits:

  • SQLite backend: Up to 1GB storage per DO instance
  • KV backend: Up to 128MB storage per DO instance

WebSocket Hibernation API

The WebSocket Hibernation API allows Durable Objects to:

  • Handle thousands of WebSocket connections per instance
  • Hibernate when idle (no messages, no events) to save costs
  • Wake up automatically when messages arrive
  • Maintain connections without incurring duration charges during idle periods

Use for: Chat rooms, real-time collaboration, multiplayer games, live updates

How Hibernation Works
  1. Active state - DO is in memory, handling messages
  2. Idle state - No messages for ~10 seconds, DO can hibernate
  3. Hibernation - In-memory state cleared, WebSockets stay connected to Cloudflare edge
  4. Wake up - New message arrives → constructor runs → handler method called

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 95 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
14
Repository forks
6
7-day trend score
14

Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 9 May 2026, 5 months ago.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/jackspace/claudeskillz/cloudflare-durable-objects/badge)](https://verified-skill.com/skills/jackspace/claudeskillz/cloudflare-durable-objects)

Something wrong with this skill or its page? Tell us.