shiichan

Say goodbye to Durable Object migration tags — meet the new declarative exports

Hi there, it's Shii! Today I found a small but genuinely nice update on the Cloudflare Changelog about Durable Objects. It's about your config file getting a lot tidier, so let's dig in!

Cloudflare Changelog developers.cloudflare.com

What was announced?

Cloudflare's Changelog announced a new exports field for your Wrangler configuration file. It replaces the old imperative migrations array as the way you manage a Durable Object class's lifecycle, switching to a declarative model instead.

You declare each Durable Object class your Worker exports, and Cloudflare compares that declaration against what is already deployed to figure out which classes need to be created, renamed, or deleted.

The story so far

Until now, managing a Durable Object class's lifecycle meant building up an ordered list of tagged steps inside the migrations array.

For example, renaming a class from ChatRoom to Room required keeping both of these tagged steps around:

{
  "migrations": [
    { "tag": "v1", "new_sqlite_classes": ["ChatRoom"] },
    {
      "tag": "v2",
      "renamed_classes": [{ "from": "ChatRoom", "to": "Room" }]
    }
  ]
}

You had to keep maintaining a chain of v1, v2, v3, and so on, and the config file only kept growing and getting harder to follow.

What changes

With exports, instead of piling up a history chain, you only declare the current state. The same rename example now looks like this:

{
  "exports": {
    "ChatRoom": {
      "type": "durable-object",
      "state": "renamed",
      "renamed_to": "Room"
    },
    "Room": { "type": "durable-object", "storage": "sqlite" }
  }
}

The exports map becomes the single source of truth, so you don't have to carry old tags like v1 and v2 forever. On top of that, Wrangler reports which classes it created, updated, deleted, renamed, or transferred on every deploy, in a structured way, so it's much easier to see exactly what changed.

Dive Deep

Each entry in exports is keyed by class name, and the state field carries the lifecycle status:

  • created (the default): a currently live class
  • deleted: a deleted (tombstoned) class
  • renamed: a class that has been renamed
  • transferred: a class transferred to another Worker
  • expecting-transfer: the receiving side of a cross-Worker transfer

The key idea is that these "no longer active" tombstone states can coexist with the classes in your source code, which avoids runtime errors mid-rollout and enables zero-downtime renames and transfers.

There's a safety net built in too: when you delete or rename a class, Cloudflare enumerates every other Worker in your account that still references that namespace, so you can redeploy them before the change lands. That should help you avoid accidentally breaking a Worker that still has a stray reference.

Existing Workers using the migrations array keep working exactly as before, so there's no rush to migrate. Cloudflare has a migration guide ready for when you do want to switch over. One thing to note: exports and migrations are mutually exclusive within a single Worker, so you can't mix the two.

Wrap-up

  • Durable Object class lifecycle management now has a declarative exports field alongside the imperative migrations array
  • You no longer build up a tag history; the exports map is the single source of truth
  • state supports five values: created, deleted, renamed, transferred, and expecting-transfer
  • Renames and transfers can happen with zero downtime, and Cloudflare automatically flags other Workers that still reference the old namespace
  • Existing migrations-based Workers keep working unchanged, and migrating is optional — just remember exports and migrations can't be mixed in the same Worker

If you've been juggling Durable Object class renames and migration tags, this update is worth trying out!