From icetea-skills
Use when working with Cloudflare Durable Objects - reviewing, creating, or modifying DO code. Triggers on: extends DurableObject, DurableObjectState, ctx.storage, blockConcurrencyWhile, alarms, WebSockets, RPC methods, SQLite storage, stateful coordination.
How this skill is triggered — by the user, by Claude, or both
Slash command
/icetea-skills:durable-objectsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Build stateful, coordinated applications on Cloudflare's edge using Durable Objects.
Build stateful, coordinated applications on Cloudflare's edge using Durable Objects.
Prefer retrieval from official docs over pre-training for Durable Objects tasks.
Fetch the relevant doc page when implementing features.
runInDurableObject(), alarm testing| Need | Example |
|---|---|
| Coordination | Chat rooms, multiplayer games, collaborative docs |
| Strong consistency | Inventory, booking systems, turn-based games |
| Per-entity storage | Multi-tenant SaaS, per-user data |
| Persistent connections | WebSockets, real-time notifications |
| Scheduled work per entity | Subscription renewals, game timeouts |
Quick test: If your database queries consistently filter by WHERE resourceID = 'abc', DOs likely simplify your architecture.
| Layer | Type | Durability | Speed | Use Case |
|---|---|---|---|---|
| In-memory variables | Transient | Lost on eviction | Fastest | Static utilities, ephemeral caches |
ctx.storage.sql | Persistent | Survives eviction | Fast | Primary data, relationships |
ctx.storage KV | Persistent | Survives eviction | Fast | Simple key-value data |
ws.serializeAttachment() | Per-connection | Survives hibernation | Fast | WebSocket session metadata |
| External (R2, D1, KV) | Global | Globally durable | Slower | Shared state, large objects |
Rule: Read from storage on every access — don't cache in instance variables. Storage reads cost ~1/1000th of writes ($0.001/M vs $1/M) and are internally cached, so the performance penalty is negligible. Instance variables are lost on eviction/hibernation and create subtle bugs. Safe instance variable uses: statically-initialized utilities (regex, bound shortcuts), ephemeral caches where under-counting on loss is acceptable.
// wrangler.jsonc
{
"durable_objects": {
"bindings": [{ "name": "MY_DO", "class_name": "MyDurableObject" }]
},
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyDurableObject"] }]
}
import { DurableObject } from "cloudflare:workers";
export interface Env {
MY_DO: DurableObjectNamespace<MyDurableObject>;
}
export class MyDurableObject extends DurableObject<Env> {
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
ctx.blockConcurrencyWhile(async () => {
this.ctx.storage.sql.exec(`
CREATE TABLE IF NOT EXISTS items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
data TEXT NOT NULL
)
`);
});
}
async addItem(data: string): Promise<number> {
const result = this.ctx.storage.sql.exec<{ id: number }>(
"INSERT INTO items (data) VALUES (?) RETURNING id",
data
);
return result.one().id;
}
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const stub = env.MY_DO.getByName("my-instance");
const id = await stub.addItem("hello");
return Response.json({ id });
},
};
getByName() for deterministic routing - Same input = same DO instance; see rule #8 for untrusted inputnew_sqlite_classes in migrationsblockConcurrencyWhile() for schema setup onlysetAlarm() replaces any existing alarmgetByName(untrustedInput) creates DOs for ANY string; validate input before creating stubsblockConcurrencyWhile() on every request (kills throughput)await between related storage writes (breaks atomicity)blockConcurrencyWhile() across fetch() or external I/O// Deterministic - preferred for most cases
const stub = env.MY_DO.getByName("room-123");
// WRONG: untrusted input creates orphan DOs
const stub = env.MY_DO.getByName(untrustedInput);
// RIGHT: validate input before creating stub
// See gotchas.md "Leaked DOs" for safe patterns
const entity = await db.findById(untrustedInput);
if (!entity) return notFound();
const stub = env.MY_DO.getByName(entity.id);
// From existing ID string
const id = env.MY_DO.idFromString(storedIdString);
const stub = env.MY_DO.get(id);
// New unique ID - store mapping externally
const id = env.MY_DO.newUniqueId();
const stub = env.MY_DO.get(id);
// SQL (synchronous, recommended)
this.ctx.storage.sql.exec("INSERT INTO t (c) VALUES (?)", value);
const rows = this.ctx.storage.sql.exec<Row>("SELECT * FROM t").toArray();
// KV (async)
await this.ctx.storage.put("key", value);
const val = await this.ctx.storage.get<Type>("key");
// Schedule (replaces existing)
await this.ctx.storage.setAlarm(Date.now() + 60_000);
// Handler
async alarm(): Promise<void> {
// Process scheduled work
// Optionally reschedule: await this.ctx.storage.setAlarm(...)
}
// Cancel
await this.ctx.storage.deleteAlarm();
// Simple: Single column addition (check if exists)
const cols = this.ctx.storage.sql.exec("PRAGMA table_info(items)").toArray();
if (!cols.some(c => c.name === 'status')) {
this.ctx.storage.sql.exec("ALTER TABLE items ADD COLUMN status TEXT");
}
// Complex: Multiple migrations (KV or SQL table tracking)
const version = this.ctx.storage.kv.get("__schema_version") ?? 0;
if (version < 1) { /* migration 1 */ }
if (version < 2) { /* migration 2 */ this.ctx.storage.kv.put("__schema_version", 2); }
Warning:
PRAGMA user_versionis not supported in Durable Objects SQLite storage. Use KV-based or SQL table tracking instead.
An alternative to KV tracking is a _sql_schema_migrations table for visible, queryable migration state — see patterns for the full example.
See Patterns: Schema Migrations for decision tree, both approaches, and trade-offs.
import { env } from "cloudflare:test";
import { describe, it, expect } from "vitest";
describe("MyDO", () => {
it("should work", async () => {
const stub = env.MY_DO.getByName("test");
const result = await stub.addItem("test");
expect(result).toBe(1);
});
});
npx claudepluginhub icetea-ai/skillsGuides collaborative design exploration before implementation: explores context, asks clarifying questions, proposes approaches, and writes a design doc for user approval.
Creates structured, bite-sized implementation plans from specs or requirements before writing code. Useful for breaking down multi-step tasks into testable steps with file structure and task boundaries.
Provides Slack GIF creation utilities with dimension/FPS/color constraints and Python PIL-based frame generation. Use for animated Slack emoji or message GIFs.