915 words
5 minutes

Cloudflare Durable Objects Exports vs Migrations: Rename and Transfer Classes Safely

2026-08-11
DevOps
Cloudflare
/
Durable Objects
/
DevOps
/
Migration

If you need to rename a Cloudflare Durable Object class, do not delete the old class and create a new one in the same blind deploy. The exports field gives Wrangler a declarative lifecycle record, but a zero-downtime rename still needs a staged rollout: alias the new class, apply a renamed tombstone, then remove the alias after the rename has propagated.

Cloudflare’s exports flow replaces the older imperative migrations array for a Worker. A Worker can use one flow at a time, so the first decision is whether you are continuing an existing migration history or deliberately moving to exports.

Choose exports or legacy migrations#

The Durable Object class exports reference describes the difference:

ConfigurationLifecycle modelImportant constraint
exportsDeclarative reconciliation of live entries and tombstonesDo not use it together with migrations in the same Worker
migrationsImperative, tagged changes from older WorkersKeep using the established history until you plan a supported migration to exports

With exports, a live SQLite-backed class is declared by its class name:

{
"durable_objects": {
"bindings": [
{
"name": "MY_DURABLE_OBJECT",
"class_name": "MyDurableObject"
}
]
},
"exports": {
"MyDurableObject": {
"type": "durable-object",
"storage": "sqlite"
}
}
}

Cloudflare provisions the namespace the first time the live entry is deployed. New namespaces created through exports use SQLite; legacy-kv is only accepted for an already-provisioned key-value namespace. Storage type is immutable after provisioning, so changing storage is not a harmless configuration edit.

Rename a class without losing its namespace#

Assume the current class is OldName and the desired name is NewName. The three-deploy rollout avoids a window in which the Worker code and namespace pointer disagree.

1. Deploy the compatibility alias#

Make the new class canonical in code, but keep the old export as an alias:

import { DurableObject } from 'cloudflare:workers';
export class NewName extends DurableObject {
// Keep the existing Durable Object behavior here.
}
export { NewName as OldName };

Keep the current exports map for this first deploy. Update application call sites and binding declarations according to the rollout you have tested, but leave the old class resolvable while the new code is rolling out.

2. Apply the rename tombstone#

On the next deploy, declare both the old tombstone and the new live entry:

{
"exports": {
"OldName": {
"type": "durable-object",
"state": "renamed",
"renamed_to": "NewName"
},
"NewName": {
"type": "durable-object",
"storage": "sqlite"
}
}
}

The renamed_to target must be a different valid JavaScript identifier, must be a live entry in the same exports map, and must not collide with an existing namespace. Cloudflare moves the namespace’s class pointer while the old alias remains available in the shipped code. A tombstone_class_still_in_code info notice is expected for this safe rollout pattern.

3. Remove the alias after rollout#

After the rename deploy has fully rolled out, remove export { NewName as OldName } and deploy again. Leave the exports entries in place until Wrangler reports the old tombstone as stale and safe to remove. Before removing it, check whether another Worker still binds to OldName; deleting a still-referenced tombstone can orphan that binding.

Deleting a class is different from renaming it. A deleted tombstone permanently removes the namespace and all stored data. Copy anything that must survive before deploying that state.

Transfer a class between Workers#

A transfer is a separate multi-deploy operation. The target Worker first declares an expecting-transfer entry, and the source Worker later commits the handoff with a transferred tombstone:

// Target Worker: prepare to receive the namespace.
{
"exports": {
"MyDO": {
"type": "durable-object",
"state": "expecting-transfer",
"storage": "sqlite",
"transfer_from": "source-worker"
}
}
}
// Source Worker: commit the transfer.
{
"exports": {
"MyDO": {
"type": "durable-object",
"state": "transferred",
"transferred_to": "target-worker"
}
}
}

Deploy the target first, then the source. After the source deploy commits the handoff and the rollout completes, add the target binding and remove the source class only when no other Worker references it. The source and target must be in the same Cloudflare account and compatible dispatch-namespace context.

Read reconciliation output before cleanup#

wrangler deploy prints a Durable Object exports reconciliation block when it applies a lifecycle change or has an info notice. Treat it as part of the migration result:

Terminal window
npx wrangler deploy

Look for Renamed, Transferred, Transfer pending, referencing_scripts, and Safe to remove from exports. A stale tombstone is a cleanup hint, not permission to delete it blindly. Remove it only after all bindings and code references have moved.

For request-level tests around a Worker that uses Durable Objects, Cloudflare Workers createTestHarness() can exercise the binding and failure path before a production deploy.

A safe decision checklist#

  1. Confirm whether the Worker already uses migrations or exports.
  2. Identify every binding and Worker that references the old class name.
  3. Decide whether the operation is a rename, a transfer, or an intentional destructive delete.
  4. Use the required tombstone and live-entry pair for the operation.
  5. Deploy each stage separately and inspect reconciliation output.
  6. Remove stale aliases and tombstones only after the namespace has no external references.

FAQ#

Q: Can I use exports and migrations together?#

A: No. Cloudflare supports both flows, but a Worker can use only one at a time. Keep an existing migration history until you plan the documented move to exports.

Q: Does a deleted tombstone act like a soft delete?#

A: No. It permanently deletes the Durable Object namespace and its stored data. Export or copy required data before deploying a delete state.

Q: Why is a rename split across three deploys?#

A: The namespace class pointer and the Worker code do not change atomically at runtime. Keeping the old alias through the rename deploy prevents a short rollout window from resolving a class that the code no longer exports.

References:

Cloudflare Docs: Durable Object class exports

Cloudflare Changelog: Declarative Durable Object class exports

Cloudflare Docs: Durable Object storage backends

Cloudflare Workers integration test harness

Cloudflare Durable Objects Exports vs Migrations: Rename and Transfer Classes Safely
https://laplusda.com/en/posts/cloudflare-durable-objects-exports-migration/
Author
Zero
Published at
2026-08-11
License
CC BY-NC-SA 4.0
Was this article useful?

Report a typo or broken link, or suggest a related topic.