7.3 KiB
Durable Objects Migrations Guide
Complete guide to managing DO class lifecycles with migrations.
Why Migrations?
Migrations tell Cloudflare Workers runtime about changes to Durable Object classes:
Required for:
- ✅ Creating new DO class
- ✅ Renaming DO class
- ✅ Deleting DO class
- ✅ Transferring DO class to another Worker
NOT required for:
- ❌ Code changes to existing DO class
- ❌ Storage schema changes within DO
Migration Types
1. Create New DO Class
{
"durable_objects": {
"bindings": [
{
"name": "COUNTER",
"class_name": "Counter"
}
]
},
"migrations": [
{
"tag": "v1", // Unique migration identifier
"new_sqlite_classes": [ // SQLite backend (recommended)
"Counter"
]
}
]
}
For KV backend (legacy):
{
"migrations": [
{
"tag": "v1",
"new_classes": ["Counter"] // KV backend (128MB limit)
}
]
}
CRITICAL:
- ✅ Use
new_sqlite_classesfor new DOs (1GB storage, atomic operations) - ❌ Cannot change KV backend to SQLite after deployment
2. Rename DO Class
{
"durable_objects": {
"bindings": [
{
"name": "MY_DO",
"class_name": "NewClassName" // Updated class name
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["OldClassName"]
},
{
"tag": "v2", // New migration tag
"renamed_classes": [
{
"from": "OldClassName",
"to": "NewClassName"
}
]
}
]
}
What happens:
- ✅ All existing DO instances keep their data
- ✅ Old bindings automatically forward to new class
- ✅
idFromName('foo')still routes to same instance - ⚠️ Must export new class in Worker code
3. Delete DO Class
{
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["Counter"]
},
{
"tag": "v2",
"deleted_classes": ["Counter"] // Mark for deletion
}
]
}
What happens:
- ✅ All DO instances immediately deleted
- ✅ All storage permanently deleted
- ⚠️ CANNOT UNDO - data is gone forever
Before deleting:
- Export data if needed
- Update Workers that reference this DO
- Consider rename instead (if migrating)
4. Transfer DO Class to Another Worker
Destination Worker:
{
"durable_objects": {
"bindings": [
{
"name": "TRANSFERRED_DO",
"class_name": "TransferredClass"
}
]
},
"migrations": [
{
"tag": "v1",
"transferred_classes": [
{
"from": "OriginalClass",
"from_script": "original-worker", // Source Worker name
"to": "TransferredClass"
}
]
}
]
}
What happens:
- ✅ DO instances move to new Worker
- ✅ All storage is transferred
- ✅ Old bindings automatically forward to new Worker
- ⚠️ Must export new class in destination Worker
Migration Rules
Tags Must Be Unique
// ✅ CORRECT
{
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["A"] },
{ "tag": "v2", "new_sqlite_classes": ["B"] },
{ "tag": "v3", "renamed_classes": [{ "from": "A", "to": "C" }] }
]
}
// ❌ WRONG: Duplicate tag
{
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["A"] },
{ "tag": "v1", "new_sqlite_classes": ["B"] } // ERROR
]
}
Tags Are Append-Only
// ✅ CORRECT: Add new tag
{
"migrations": [
{ "tag": "v1", ... },
{ "tag": "v2", ... } // Append
]
}
// ❌ WRONG: Remove or reorder
{
"migrations": [
{ "tag": "v2", ... } // Can't remove v1
]
}
Migrations Are Atomic
⚠️ Cannot use gradual deployments with migrations
- All DO instances migrate at once when you deploy
- No partial rollout support
- Use canary releases at Worker level, not DO level
Migration Gotchas
Global Uniqueness
DO class names are globally unique per account.
// Worker A
export class Counter extends DurableObject { }
// Worker B
export class Counter extends DurableObject { }
// ❌ ERROR: Class name "Counter" already exists in account
Solution: Use unique class names (e.g., prefix with Worker name)
// Worker A
export class CounterA extends DurableObject { }
// Worker B
export class CounterB extends DurableObject { }
Cannot Enable SQLite on Existing KV-backed DO
// Deployed with:
{ "tag": "v1", "new_classes": ["Counter"] } // KV backend
// ❌ WRONG: Cannot change to SQLite
{ "tag": "v2", "renamed_classes": [{ "from": "Counter", "to": "CounterSQLite" }] }
{ "tag": "v3", "new_sqlite_classes": ["CounterSQLite"] }
// ✅ CORRECT: Create new class instead
{ "tag": "v2", "new_sqlite_classes": ["CounterV2"] }
// Then migrate data from Counter to CounterV2
Code Changes Don't Need Migrations
// ✅ CORRECT: Just deploy code changes
export class Counter extends DurableObject {
async increment(): Promise<number> {
// Changed implementation
let value = await this.ctx.storage.get<number>('count') || 0;
value += 2; // Changed from += 1
await this.ctx.storage.put('count', value);
return value;
}
}
// No migration needed - deploy directly
Only schema changes (new/rename/delete/transfer) need migrations.
Environment-Specific Migrations
You can define migrations per environment:
{
// Top-level (default) migrations
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["Counter"] }
],
"env": {
"production": {
// Production-specific migrations override top-level
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["Counter"] },
{ "tag": "v2", "new_sqlite_classes": ["Analytics"] }
]
}
}
}
Rules:
- If migration defined at environment level, it overrides top-level
- If NOT defined at environment level, inherits top-level
Migration Workflow
Example: Rename DO Class
Step 1: Current state (v1)
{
"durable_objects": {
"bindings": [{ "name": "MY_DO", "class_name": "OldName" }]
},
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["OldName"] }
]
}
Step 2: Update wrangler.jsonc
{
"durable_objects": {
"bindings": [{ "name": "MY_DO", "class_name": "NewName" }]
},
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["OldName"] },
{ "tag": "v2", "renamed_classes": [{ "from": "OldName", "to": "NewName" }] }
]
}
Step 3: Update Worker code
// Rename class
export class NewName extends DurableObject { }
export default NewName;
Step 4: Deploy
npx wrangler deploy
Migration applies atomically on deploy.
Troubleshooting
Error: "Migration tag already exists"
Cause: Trying to reuse a migration tag
Solution: Use a new, unique tag
Error: "Class not found"
Cause: Class not exported from Worker
Solution: Ensure export default MyDOClass;
Error: "Cannot enable SQLite on existing class"
Cause: Trying to migrate KV-backed DO to SQLite
Solution: Create new SQLite-backed class, migrate data manually
Official Docs: https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/