cloudflare-durable-objects
from jezweb/claude-skills
Skills for Claude Code CLI such as full stack dev Cloudflare, React, Tailwind v4, and AI integrations.
213 stars24 forksUpdated Jan 25, 2026
npx skills add https://github.com/jezweb/claude-skills --skill cloudflare-durable-objectsSKILL.md
Cloudflare Durable Objects
Status: Production Ready ✅ Last Updated: 2026-01-21 Dependencies: cloudflare-worker-base (recommended) Latest Versions: wrangler@4.58.0, @cloudflare/workers-types@4.20260109.0 Official Docs: https://developers.cloudflare.com/durable-objects/
Recent Updates (2025):
- Oct 2025: WebSocket message size 1 MiB → 32 MiB, Data Studio UI for SQLite DOs (view/edit storage in dashboard)
- Aug 2025:
getByName()API shortcut for named DOs - June 2025: @cloudflare/actors library (beta) - recommended SDK with migrations, alarms, Actor class pattern. Note: Beta stability - see active issues before production use (RPC serialization, vitest integration, memory management)
- May 2025: Python Workers support for Durable Objects
- April 2025: SQLite GA with 10GB storage (beta → GA, 1GB → 10GB), Free tier access
- Feb 2025: PRAGMA optimize support, improved error diagnostics with reference IDs
Quick Start
Scaffold new DO project:
npm create cloudflare@latest my-durable-app -- --template=cloudflare/durable-objects-template --ts
Or add to existing Worker:
// src/counter.ts - Durable Object class
import { DurableObject } from 'cloudflare:workers';
export class Counter extends DurableObject {
async increment(): Promise<number> {
let value = (await this.ctx.storage.get<number>('value')) || 0;
await this.ctx.storage.put('value', ++value);
return value;
}
}
export default Counter; // CRITICAL: Export required
// wrangler.jsonc - Configuration
{
"durable_objects": {
"bindings": [{ "name": "COUNTER", "class_name": "Counter" }]
},
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["Counter"] } // SQLite backend (10GB limit)
]
}
// src/index.ts - Worker
import { Counter } from './counter';
export { Counter };
export default {
async fetch(request: Request, env: { COUNTER: DurableObjectNamespace<Counter> }) {
const stub = env.COUNTER.getByName('global-counter'); // Aug 2025: getByName() shortcut
return new Response(`Count: ${await stub.increment()}`);
}
};
DO Class Essentials
import { DurableObject } from 'cloudflare:workers';
export class MyDO extends DurableObject {
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env); // REQUIRED first line
// Load state before requests (optional)
ctx.blockConcurrencyWhile(async () => {
this.value = await ctx.storage.get('key') || defaultValue;
});
}
// RPC methods (recommended)
async myMethod(): Promise<string> { return 'Hello'; }
// HTTP fetch handler (optional)
async fetch(request: Request): Promise<Response> { return new Response('OK'); }
}
export default MyDO; // CRITICAL: Export required
// Worker must export DO class too
import { MyDO } from './my-do';
export { MyDO };
Constructor Rules:
- ✅ Call
super(ctx, env)first - ✅ Keep minimal - heavy work blocks hibernation wake
- ✅ Use
ctx.blockConcurrencyWhile()for storage initialization - ❌ Never
setTimeout/setInterval(use alarms) - ❌ Don't rely on in-memory state with WebSockets (persist to storage)
Storage API
Two backends available:
- SQLite (recommended): 10GB storage, SQL queries, atomic operations, PITR
- KV: 128MB storage, key-value only
Enable SQLite in migrations:
{ "migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyDO"] }] }
SQL API (SQLite backend)
export class MyDO extends DurableObject {
sql: SqlStorage;
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
this.sql = ctx.storage.sql;
this.sql.exec(`
CREATE TABLE IF NOT EXISTS messages (id INTEGER PRIMARY KEY, text TEXT, created_at INTEGER);
CREATE INDEX IF NOT EXISTS idx_created ON messages(created_at);
PRAGMA optimize; // Feb 2025: Query performance optimization
`);
}
async addMessage(text: string): Promise<number> {
const cursor = this.sql.exec('INSERT INTO messages (text, created_at) VALUES (?, ?) RETURNING id', text, Date.now());
return cursor.one<{ id: number }>().id;
}
async getMessages(limit = 50): Promise<any[]> {
return this.sql.exec('SELECT * FROM messages ORDER BY created_at DESC LIMIT ?', limit).toArray();
}
}
SQL Methods:
sql.exec(query, ...params)→ cursorcursor.one<T>()→ single row (throws if none)cursor.one<T>({ allowNone: true })→ row or nullcursor.toArray<T>()→ all rowsctx.storage.transactionSync(() => { ... })→ atomic multi-statement
Best Practices:
- ✅ Use
?placeholders for parameterized queries - ✅ Create indexes on frequently queried columns
- ✅ Use
PRAGMA optimizeafter schema changes - ✅ Add
STRICTkeyword to table definitions to enforce type affinity and catch type mismatches early - ✅ Convert booleans to integers (0/1)
...
Repository
jezweb/claude-skillsParent repository
Repository Stats
Stars213
Forks24
LicenseMIT License