---
description: Use the declarative `exports` field in `wrangler.json` to manage Durable Object class lifecycle — create, delete, rename, and transfer Durable Object classes.
title: Durable Object class exports
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/durable-objects/llms.txt  
> Use this file to discover all available pages before exploring further.

# Durable Object class exports

Last updated Aug 25, 2026|Copy as Markdown|[View as Markdown](https://sm-rtk-allowlist-page.previews.developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/index.md)|[Agent setup](https://sm-rtk-allowlist-page.previews.developers.cloudflare.com/agent-setup/)

The `exports` field in your [Wrangler configuration file](https://sm-rtk-allowlist-page.previews.developers.cloudflare.com/workers/wrangler/configuration/) is the declarative way to manage [Durable Object class](https://sm-rtk-allowlist-page.previews.developers.cloudflare.com/durable-objects/) lifecycle. You declare each Durable Object class your Worker exports — along with whether it is live, deleted, renamed, or transferred — and Cloudflare reconciles your declaration against the namespaces that have already been provisioned for your Worker.

This page covers how to:

* [Create](#define-a-durable-object-class) a new Durable Object class.
* [Delete](#delete-a-durable-object-class) a Durable Object class and its data.
* [Rename](#rename-a-durable-object-class) a Durable Object class.
* [Transfer](#transfer-a-durable-object-class-between-workers) a Durable Object class between Workers.

Looking for the legacy \`migrations\` array?

The `exports` field replaces the imperative [migrations array](https://sm-rtk-allowlist-page.previews.developers.cloudflare.com/durable-objects/reference/durable-object-class-migrations-legacy/) used by older Workers. Both flows are supported, but a Worker can only use one at a time. To move an existing Worker from `migrations` to `exports`, refer to [Migrate from the legacy migrations flow](#migrate-from-the-legacy-migrations-flow).

## How `exports` works

When you deploy a Worker that declares `exports`, Cloudflare compares three sources of truth:

1. **Your code** — the set of Durable Object classes the Worker actually exports.
2. **Your `exports` configuration** — what you have declared about each class.
3. **Provisioned state** — the Durable Object namespaces that already exist for this Worker.

A class that appears only in your code is ignored until you declare it in `exports`; Cloudflare does not provision a namespace implicitly. Other disagreements, such as a live entry whose class is missing from code or a provisioned namespace with no matching entry, are surfaced as structured errors or, where the intent is unambiguous, actions that Cloudflare applies for you.

A minimal `exports` block declares each Durable Object class as a live entry:

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

```toml
[[durable_objects.bindings]]
name = "MY_DURABLE_OBJECT"
class_name = "MyDurableObject"

[exports.MyDurableObject]
type = "durable-object"
storage = "sqlite"
```

To declare a destructive operation — deleting, renaming, or transferring a class — you change the entry's `state` to a tombstone variant. The class name remains the same; the value tells Cloudflare what to do with the existing namespace.

| Operation                              | state                  | Required fields         |
| -------------------------------------- | ---------------------- | ----------------------- |
| Define a new class (default)           | "created" (or omitted) | storage                 |
| Delete a class                         | "deleted"              | _(none)_                |
| Rename a class                         | "renamed"              | renamed\_to             |
| Transfer a class to another Worker     | "transferred"          | transferred\_to         |
| Receive a transfer from another Worker | "expecting-transfer"   | storage, transfer\_from |

The rest of this page describes each operation in detail and links to the [exports configuration reference](#exports-configuration-reference) for the full schema.

## Define a Durable Object class

To define a new Durable Object class, add an entry to `exports` keyed by the class name and set `storage` to `"sqlite"`.

Recommended SQLite-backed Durable Objects

Cloudflare recommends all new Durable Object namespaces use the [SQLite storage backend](https://sm-rtk-allowlist-page.previews.developers.cloudflare.com/durable-objects/best-practices/access-durable-objects-storage/#create-sqlite-backed-durable-object-class). These Durable Objects can continue to use storage [key-value API](https://sm-rtk-allowlist-page.previews.developers.cloudflare.com/durable-objects/api/sqlite-storage-api/#synchronous-kv-api).

Additionally, SQLite-backed Durable Objects allow you to store more types of data (such as tables), and offer Point In Time Recovery API which can restore a Durable Object's embedded SQLite database contents (both SQL data and key-value data) to any point in the past 30 days.

Creating new namespaces with the [key-value storage backend](https://sm-rtk-allowlist-page.previews.developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/#storage-backends) is no longer supported for accounts without an existing key-value-backed namespace. The key-value storage backend remains available for existing namespaces, and a migration path from the key-value storage backend to the SQLite storage backend will be available in the future.

1. Add the class to your Worker code and export it:  
```ts  
import { DurableObject } from "cloudflare:workers";  
export class MyDurableObject extends DurableObject {  
  // ...  
}  
```
2. Add a binding for the class (if your Worker needs to access it through `env`) and declare the class in `exports`:  
```jsonc  
{  
  "durable_objects": {  
    "bindings": [  
      {  
        "name": "MY_DURABLE_OBJECT",  
        "class_name": "MyDurableObject"  
      }  
    ]  
  },  
  "exports": {  
    "MyDurableObject": {  
      "type": "durable-object",  
      "storage": "sqlite"  
    }  
  }  
}  
```  
```toml  
[[durable_objects.bindings]]  
name = "MY_DURABLE_OBJECT"  
class_name = "MyDurableObject"  
[exports.MyDurableObject]  
type = "durable-object"  
storage = "sqlite"  
```
3. Deploy the Worker:  
```sh  
npx wrangler deploy  
```

Cloudflare provisions a namespace for the class the first time you deploy. On subsequent deploys with the same entry, no namespace changes are made — the entry simply confirms that the class is still live.

Note

`storage` is required for live entries. New Durable Object namespaces created through `exports` always use the SQLite storage backend. The `legacy-kv` value is only accepted for namespaces that were already provisioned with key-value storage; refer to [Storage backends](#storage-backends).

## Delete a Durable Object class

Replace the live entry with a `deleted` tombstone to retire a Durable Object class. Deleting a class removes its namespace and **all of its stored data permanently** — this is not a soft delete.

1. Remove the class from your Worker code.
2. Remove the binding for the class from `durable_objects.bindings`.
3. Change the `exports` entry for the class to a `deleted` tombstone:  
```jsonc  
{  
  "exports": {  
    "OldDurableObject": {  
      "type": "durable-object",  
      "state": "deleted"  
    }  
  }  
}  
```  
```toml  
[exports.OldDurableObject]  
type = "durable-object"  
state = "deleted"  
```
4. Deploy the Worker:  
```sh  
npx wrangler deploy  
```

Deleting a class destroys its data

There is no Trash for Durable Object namespaces deleted through `exports`. Copy any data you need to keep to another location before deploying a `deleted` tombstone.

A `deleted` tombstone has two preconditions enforced at deploy time:

* The class must not be present in your Worker code. Cloudflare will not delete a namespace whose class is still being shipped, because runtime bindings would resolve to a deleted namespace.
* No other Worker in your account may bind to the namespace. If another Worker still binds to the class, the deploy is rejected with `tombstone_delete_blocked_by_external_bindings` and the list of referencing scripts is returned. Redeploy those Workers without the binding first, then re-run your deploy.

Once the namespace has been deleted, the tombstone becomes stale. Cloudflare reports this in the [reconciliation output](#reading-the-reconciliation-output) and lists the entry in `removable_entries` so you can safely remove it from `exports`.

## Rename a Durable Object class

Renaming a Durable Object class moves stored data from one class to another within the same Worker. Renaming requires:

* A `renamed` tombstone keyed by the old class name, with `renamed_to` pointing at the new class name.
* A live entry for the new class name in the same `exports` map (so the data has somewhere to land).

For a brand-new deploy where the old class is no longer in your code, a single deploy is enough:

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

```toml
[exports.OldName]
type = "durable-object"
state = "renamed"
renamed_to = "NewName"

[exports.NewName]
type = "durable-object"
storage = "sqlite"
```

After the rename applies, the namespace's class name is updated to `NewName` and runtime bindings that reference the new class name resolve to the same data.

### Avoid downtime during a rename

A Durable Object class rename involves two updates that are not perfectly atomic at the runtime layer: the namespace's class pointer and the Worker code that exports the class. During a deploy rollout, one update may be visible before the other for a few seconds. To avoid runtime errors during this window, use a **three-deploy rename**:

1. **Alias the new name to the old class.** Add the new class name to your code as the canonical class, and re-export it under the old name so existing instances keep resolving:  
```ts  
import { DurableObject } from "cloudflare:workers";  
export class NewName extends DurableObject {  
  // ...  
}  
export { NewName as OldName };  
```  
Leave `exports` unchanged. Deploy.
2. **Apply the rename while the alias is still in place.** Update `exports` to add the `renamed` tombstone and the new live entry:  
```jsonc  
{  
  "exports": {  
    "OldName": {  
      "type": "durable-object",  
      "state": "renamed",  
      "renamed_to": "NewName"  
    },  
    "NewName": {  
      "type": "durable-object",  
      "storage": "sqlite"  
    }  
  }  
}  
```  
```toml  
[exports.OldName]  
type = "durable-object"  
state = "renamed"  
renamed_to = "NewName"  
[exports.NewName]  
type = "durable-object"  
storage = "sqlite"  
```  
Deploy. Cloudflare applies the rename and surfaces a `tombstone_class_still_in_code` info notice — this is expected and confirms the safe rollout pattern.
3. **Remove the alias.** Once the previous deploy has fully rolled out, remove the `OldName` alias from your code:  
```ts  
import { DurableObject } from "cloudflare:workers";  
export class NewName extends DurableObject {  
  // ...  
}  
```  
Leave `exports` unchanged. Deploy. The `OldName` tombstone is now stale and Cloudflare lists it in `removable_entries` — you can delete the entry from `exports` in your next config edit.

The `renamed_to` target must:

* Be a valid JavaScript identifier and differ from the source class name.
* Appear as a live entry (`state: "created"` or omitted) in the same `exports` map. Cloudflare rejects a `renamed_to` value that names another tombstone or is missing entirely.
* Not collide with an existing namespace under the same name on this Worker. If a namespace already exists under the target name, delete it via its own `deleted` tombstone in a prior deploy first.

## Transfer a Durable Object class between Workers

Transferring moves an existing Durable Object namespace from one Worker (the **source**) to another Worker (the **target**) in the same account. Because two Workers must coordinate, transfer is a multi-deploy flow.

The target Worker declares an `expecting-transfer` entry that names the source Worker. The source Worker declares a `transferred` tombstone that names the target Worker. The actual handoff commits when the source Worker's deploy lands.

The recommended sequence is four deploys:

1. **Source Worker — initial state.** The source declares `MyDO` as a live class. Nothing else has changed yet.  
```jsonc  
{  
  "exports": {  
    "MyDO": { "type": "durable-object", "storage": "sqlite" }  
  }  
}  
```  
```toml  
[exports.MyDO]  
type = "durable-object"  
storage = "sqlite"  
```
2. **Target Worker — receive the transfer.** The target adds `MyDO` to its code and declares an `expecting-transfer` entry naming the source Worker. Do **not** add a `durable_objects.bindings` entry for `MyDO` on the target yet — Cloudflare does not route self-referencing bindings through the source's namespace during this phase.  
```jsonc  
{  
  "exports": {  
    "MyDO": {  
      "type": "durable-object",  
      "state": "expecting-transfer",  
      "storage": "sqlite",  
      "transfer_from": "source-worker"  
    }  
  }  
}  
```  
```toml  
[exports.MyDO]  
type = "durable-object"  
state = "expecting-transfer"  
storage = "sqlite"  
transfer_from = "source-worker"  
```  
Deploy the target. Cloudflare records a pending transfer and emits a `Transfer pending` notice in the [reconciliation output](#reading-the-reconciliation-output).
3. **Source Worker — commit the transfer.** Change the source's `MyDO` entry to a `transferred` tombstone naming the target Worker. Leave the class in the source code for now:  
```jsonc  
{  
  "exports": {  
    "MyDO": {  
      "type": "durable-object",  
      "state": "transferred",  
      "transferred_to": "target-worker"  
    }  
  }  
}  
```  
```toml  
[exports.MyDO]  
type = "durable-object"  
state = "transferred"  
transferred_to = "target-worker"  
```  
Deploy the source. Cloudflare matches the pending transfer and atomically reassigns the namespace to the target Worker. The reconciliation output reports `Transferred (committed): MyDO → target-worker`.  
If the source Worker still needs to access `MyDO` after the transfer commits, update its `durable_objects.bindings` entry to point at the target Worker with `script_name`:  
```jsonc  
{  
  "durable_objects": {  
    "bindings": [  
      {  
        "name": "MY_DURABLE_OBJECT",  
        "class_name": "MyDO",  
        "script_name": "target-worker"  
      }  
    ]  
  }  
}  
```  
```toml  
[[durable_objects.bindings]]  
name = "MY_DURABLE_OBJECT"  
class_name = "MyDO"  
script_name = "target-worker"  
```  
If the source Worker no longer needs to access `MyDO`, remove the binding when you remove `MyDO` from the source Worker's code.
4. **Target Worker — bind the class.** Once the source's deploy has fully rolled out, redeploy the target with a `durable_objects.bindings` entry for `MyDO`. The binding now resolves to the namespace on the target Worker.  
```jsonc  
{  
  "durable_objects": {  
    "bindings": [  
      {  
        "name": "MY_DURABLE_OBJECT",  
        "class_name": "MyDO"  
      }  
    ]  
  },  
  "exports": {  
    "MyDO": { "type": "durable-object", "storage": "sqlite" }  
  }  
}  
```  
```toml  
[[durable_objects.bindings]]  
name = "MY_DURABLE_OBJECT"  
class_name = "MyDO"  
[exports.MyDO]  
type = "durable-object"  
storage = "sqlite"  
```

After the handoff has rolled out everywhere, you can remove `MyDO` from the source Worker's code and delete the `transferred` tombstone from the source's `exports` map. While other Workers in the account still bind to `MyDO` on the source, the reconciliation output lists them in `referencing_scripts`; redeploy each of them with bindings re-pointed at the target before removing the source tombstone.

### Cancel a pending transfer

A pending transfer persists until the source Worker commits with a `transferred` tombstone, or the target Worker cancels by removing the `expecting-transfer` entry. To cancel, redeploy the target without the entry (or replace it with a normal live entry). Cloudflare deletes the pending record and the source Worker keeps its namespace.

### Transfer constraints

* Both Workers must live in the same Cloudflare account.
* Cross-[dispatch-namespace](https://sm-rtk-allowlist-page.previews.developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/) transfers are not supported. The source and target Workers must both be in the same dispatch-namespace context (or both be outside any dispatch namespace).
* A target Worker can hold only one pending phase-1 hint per class at a time. To redirect a pending transfer to a different source, cancel the current pending transfer first.

## Storage backends

Live entries (`state: "created"` and `state: "expecting-transfer"`) must declare a `storage` value:

* `"sqlite"` selects the SQLite storage backend. This is the recommended and only path for new namespaces. SQLite-backed namespaces support [SQL](https://sm-rtk-allowlist-page.previews.developers.cloudflare.com/durable-objects/api/sqlite-storage-api/), [Point-in-Time Recovery](https://sm-rtk-allowlist-page.previews.developers.cloudflare.com/durable-objects/api/sqlite-storage-api/#point-in-time-recovery-api), and a higher per-object storage limit.
* `"legacy-kv"` selects the key-value storage backend. Cloudflare only accepts this value for namespaces that were already provisioned with key-value storage (typically Workers that started on the [legacy migrations array](https://sm-rtk-allowlist-page.previews.developers.cloudflare.com/durable-objects/reference/durable-object-class-migrations-legacy/) before SQLite was the default). You cannot create a **new** key-value-backed namespace through `exports`.

Note

Durable Objects are available both on Workers Free and Workers Paid plans.

* **Workers Free plan**: Only Durable Objects with [SQLite storage backend](https://sm-rtk-allowlist-page.previews.developers.cloudflare.com/durable-objects/best-practices/access-durable-objects-storage/#create-sqlite-backed-durable-object-class) are available.
* **Workers Paid plan**: Durable Objects with the SQLite storage backend are available. The [key-value storage backend](https://sm-rtk-allowlist-page.previews.developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/#storage-backends) is only available to accounts that already have a key-value-backed namespace.

If you wish to downgrade from a Workers Paid plan to a Workers Free plan, you must first ensure that you have deleted all Durable Object namespaces with the key-value storage backend.

Storage type is immutable once a namespace exists. Cloudflare rejects an `exports` change that switches a provisioned namespace from `sqlite` to `legacy-kv` or vice versa with the error `storage_type_mismatch`. To genuinely change storage backends, delete the namespace via a `deleted` tombstone and re-provision it under the new backend (with full data loss in between).

## Reading the reconciliation output

When a deployment applies a Durable Object class lifecycle change, `wrangler deploy` prints a `Durable Object exports reconciliation` block. The block also appears when reconciliation produces warnings, info notices, or removable entries. Wrangler omits the block when nothing changed and there are no notices.

A typical output looks like:

```txt
Durable Object exports reconciliation:
  Created: ChatRoom
  Renamed: OldRoom → ChatRoom
  Transferred (committed): Widget → target-worker
  Transfer pending: Incoming ← source-worker

  Info:
    [tombstone_class_still_in_code] OldRoom: Tombstone of type 'renamed' applied. Class 'OldRoom' is still exported in code; this is the supported pattern for zero-downtime rename rollouts.
    [stale_tombstone] OldGone: Tombstone of type 'deleted' for class 'OldGone' has no effect (no namespace exists with this class name). Safe to remove from `exports`.

  Safe to remove from `exports`: OldGone
```

The block has four sections:

* **Action lines** (`Created`, `Updated`, `Deleted`, `Renamed`, `Transferred`, `Transfer pending`) report the changes Cloudflare applied during this deploy.
* **Warnings** (yellow) flag conditions that should be investigated but do not block the deploy. The set of warning scenarios is reserved for future use; current Cloudflare deploys do not emit them.
* **Info** (dimmed) reports non-blocking notices: stale tombstones that no longer apply, and tombstones applied while the source class is still in code (the supported pattern for zero-downtime rollouts).
* **Safe to remove from `exports`** lists tombstone entries that are stale and have no other Workers in the account binding to the source class. You can delete these entries from your `exports` map on your next config edit.

Info entries can include a `referencing_scripts` list — other Workers in the account whose bindings still resolve to the affected namespace. Redeploy those Workers with bindings re-pointed at the new class name before removing the tombstone, or you will orphan their bindings.

If the deploy fails, the output shows a structured error per class:

```txt
✘ [orphaned_provisioned_namespace] class 'Bar': A namespace exists for 'Bar' but no `exports` entry declares it.
    Suggestion: add an `exports` entry for 'Bar', or add a `deleted` tombstone to remove the namespace.
    Referencing scripts: worker-foo, worker-bar
```

All class-level errors from a single deploy are reported together so you can fix them in one round trip. Refer to the [error reference](#error-reference) for the full list of error scenarios.

## Stale tombstones and cleanup

A tombstone becomes **stale** when applying it produces no state change — for example, a `deleted` tombstone for a class whose namespace has already been removed, or a `renamed` tombstone after the rename has already landed.

Cloudflare emits a `stale_tombstone` info notice on every deploy where a tombstone is stale, until you remove the entry from `exports`. The notice is intentionally repeated so you do not forget to clean up.

For renamed and transferred tombstones, Cloudflare also enumerates other Workers in your account whose `durable_objects.bindings` entries still reference the source class name. These appear as `referencing_scripts` on the info notice. While `referencing_scripts` is non-empty, removing the tombstone could orphan those bindings — redeploy the referencing Workers with bindings re-pointed at the new class first.

The top-level "Safe to remove from `exports`" line in the reconciliation output names every stale tombstone whose `referencing_scripts` is empty. Use that list as the authoritative "you can delete these now" hint.

## Environments, dispatch namespaces, and previews

`exports` can be specified at the top level of your Wrangler configuration file and overridden per [environment](https://sm-rtk-allowlist-page.previews.developers.cloudflare.com/durable-objects/reference/environments/):

```jsonc
{
	// top-level exports apply to the default environment
	"exports": {
		"MyDO": { "type": "durable-object", "storage": "sqlite" }
	},
	"env": {
		"staging": {
			// override for the staging environment
			"exports": {
				"MyDO": { "type": "durable-object", "storage": "sqlite" }
			}
		}
	}
}
```

```toml
[exports.MyDO]
type = "durable-object"
storage = "sqlite"

[env.staging.exports.MyDO]
type = "durable-object"
storage = "sqlite"
```

If `exports` is only declared at the top level, named environments inherit the same value. Each environment maintains its own provisioned namespaces, so tombstones apply only within the environment they are declared in.

[Preview deployments](https://sm-rtk-allowlist-page.previews.developers.cloudflare.com/workers/versions-and-deployments/preview-urls/) and [dispatch namespaces](https://sm-rtk-allowlist-page.previews.developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/) follow the same rules. Cross-dispatch-namespace transfers are not supported — the source and target Workers in a `transferred` / `expecting-transfer` pair must both live in the same dispatch-namespace context (or both be outside any dispatch namespace).

## `exports` configuration reference

The `exports` field is a map keyed by Durable Object class name. Each value is an object whose fields depend on `state`:

* `type` `string` required

  * For Durable Object class entries, set this to `"durable-object"`.
* `state` `string` optional

  * The lifecycle state. One of `"created"` (the default, omit to use), `"deleted"`, `"renamed"`, `"transferred"`, or `"expecting-transfer"`.
* `storage` `string` conditional

  * Required when `state` is `"created"` or `"expecting-transfer"`. One of `"sqlite"` (recommended, the only valid value for new namespaces) or `"legacy-kv"` (only for existing key-value-backed namespaces). Forbidden on tombstone states.
* `renamed_to` `string` conditional

  * Required when `state` is `"renamed"`. The destination class name. Must be a valid JavaScript identifier, differ from the source class name, and appear as a live entry in the same `exports` map.
* `transferred_to` `string` conditional

  * Required when `state` is `"transferred"`. The name of the target Worker that will receive the namespace.
* `transfer_from` `string` conditional

  * Required when `state` is `"expecting-transfer"`. The name of the source Worker the namespace is being transferred from.

The following table lists the allowed and forbidden fields for each `state`:

| state                | Required                | Forbidden                                             |
| -------------------- | ----------------------- | ----------------------------------------------------- |
| "created" (default)  | storage                 | renamed\_to, transferred\_to, transfer\_from          |
| "deleted"            | _(none)_                | storage, renamed\_to, transferred\_to, transfer\_from |
| "renamed"            | renamed\_to             | storage, transferred\_to, transfer\_from              |
| "transferred"        | transferred\_to         | storage, renamed\_to, transfer\_from                  |
| "expecting-transfer" | storage, transfer\_from | renamed\_to, transferred\_to                          |

## Error reference

When a deploy fails reconciliation, Cloudflare returns one error per class along with a structured `scenario` tag, a human-readable message, and where applicable a `suggestion` and `referencing_scripts` list:

| Scenario                                              | Meaning                                                                                                                                                        | How to fix                                                                                                                                   |
| ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| provisioned\_class\_missing\_from\_config             | A namespace exists for a class that the Worker code still exports, but exports has no entry for it.                                                            | Add a live entry (state: "created") to keep the namespace, or add a tombstone (deleted, renamed, or transferred) to retire it.               |
| config\_export\_not\_in\_code                         | A live entry declares a class that the Worker code does not export.                                                                                            | Add the class to your code, or replace the entry with a tombstone.                                                                           |
| config\_references\_nonexistent\_class                | A live entry declares a class that is neither in code nor provisioned.                                                                                         | Remove the entry, or add the class to your Worker code.                                                                                      |
| orphaned\_provisioned\_namespace                      | A namespace exists for a class that is neither in code nor declared.                                                                                           | Add a tombstone for the class, or add the class back to code and exports.                                                                    |
| invalid\_export                                       | An exports entry is structurally invalid — for example, requesting legacy-kv for a new namespace, or a class that maps to more than one provisioned namespace. | Correct the entry: use "storage": "sqlite" for new namespaces, or resolve the duplicate with a deleted or renamed tombstone.                 |
| tombstone\_delete\_class\_still\_in\_code             | A deleted tombstone names a class that is still exported in code.                                                                                              | Remove the class from your code first, then deploy the tombstone.                                                                            |
| tombstone\_delete\_blocked\_by\_external\_bindings    | Another Worker in the account binds to the namespace being deleted.                                                                                            | Redeploy the referencing Workers without the binding, then re-run your deploy.                                                               |
| tombstone\_renamed\_to\_occupied                      | A renamed\_to target collides with an existing namespace under that name.                                                                                      | Delete the colliding namespace via its own deleted tombstone in a prior deploy.                                                              |
| transferred\_pending\_not\_found                      | A transferred tombstone has no matching expecting-transfer entry on the target.                                                                                | Deploy the target Worker first with an expecting-transfer entry naming this Worker.                                                          |
| transferred\_target\_missing                          | The target Worker named by transferred\_to no longer exists.                                                                                                   | Update transferred\_to to a valid target, or remove the tombstone.                                                                           |
| transferred\_target\_mismatch                         | The transferred\_to value does not match the target recorded by the pending transfer.                                                                          | Set transferred\_to to the pending transfer's target, or have the target cancel its expecting-transfer entry before redirecting.             |
| transferred\_source\_in\_dispatch\_namespace          | The source Worker declaring the transferred tombstone is in a dispatch namespace; cross-dispatch transfer is not supported.                                    | Perform the transfer within a single dispatch-namespace context.                                                                             |
| transferred\_target\_in\_dispatch\_namespace          | The target of a transferred tombstone is in a dispatch namespace; cross-dispatch transfer is not supported.                                                    | Cancel the pending transfer by having the target redeploy without expecting-transfer, or keep both Workers in the same context.              |
| phase\_one\_transfer\_after\_commit\_mismatch         | The target's namespace was transferred from a different source than the one declared.                                                                          | Remove the expecting-transfer entry — the transfer cannot be redirected after commit.                                                        |
| phase\_one\_transfer\_target\_class\_provisioned      | The target Worker already owns a namespace for the class.                                                                                                      | Replace the expecting-transfer entry with a normal live entry, or delete the existing namespace first.                                       |
| phase\_one\_transfer\_duplicate                       | Another expecting-transfer hint is already in flight for the same class.                                                                                       | Cancel the existing pending transfer first by removing or replacing its entry.                                                               |
| phase\_one\_transfer\_source\_missing                 | The source Worker named by transfer\_from does not exist in the account.                                                                                       | Correct the source Worker name.                                                                                                              |
| phase\_one\_transfer\_source\_namespace\_missing      | The source Worker has no namespace for the class.                                                                                                              | Make sure the source Worker has the class deployed before the target declares expecting-transfer.                                            |
| phase\_one\_transfer\_source\_in\_dispatch\_namespace | The source Worker is in a dispatch namespace; cross-dispatch transfer is not supported.                                                                        | Move the transfer within a single dispatch-namespace context.                                                                                |
| phase\_one\_transfer\_target\_in\_dispatch\_namespace | The target Worker is in a dispatch namespace; cross-dispatch transfer is not supported.                                                                        | Move the transfer within a single dispatch-namespace context.                                                                                |
| storage\_type\_mismatch                               | The declared storage value does not match the provisioned namespace's storage backend.                                                                         | Storage backends cannot be changed in place. Delete the namespace and re-provision it under the new backend if you genuinely need to switch. |
| free\_tier\_requires\_sqlite                          | The account's plan only supports SQLite-backed namespaces, but the entry requests legacy-kv.                                                                   | Use "storage": "sqlite".                                                                                                                     |

Note

The blocking error `tombstone_delete_class_still_in_code` (specific to `deleted` tombstones) is distinct from the non-blocking `tombstone_class_still_in_code` info notice emitted for `renamed` and `transferred` tombstones. The latter is the expected zero-downtime rollout pattern — refer to [Reading the reconciliation output](#reading-the-reconciliation-output).

## Constraints and limitations

* **`exports` and `migrations` are mutually exclusive.** A Worker configuration that contains both fields is rejected at validation. Once a Worker has been deployed with `exports`, subsequent deploys must continue to use `exports` (or neither, which reconciles against an empty config and is usually a mistake).
* **`wrangler versions upload` does not apply lifecycle changes.** Just like the legacy `migrations` array, Durable Object lifecycle changes can only be applied via `wrangler deploy`. If your Wrangler configuration contains `exports` entries, `wrangler versions upload` fails fast with an actionable error. Refer to [Deployment management - Durable Object migrations](https://sm-rtk-allowlist-page.previews.developers.cloudflare.com/workers/versions-and-deployments/deployment-management/#durable-object-migrations).
* **Gradual deployments are not supported with `exports`.** Lifecycle changes are atomic at the Cloudflare control plane and cannot be rolled out gradually. Refer to [Gradual deployments with Durable Objects - Durable Object class lifecycle changes](https://sm-rtk-allowlist-page.previews.developers.cloudflare.com/workers/versions-and-deployments/gradual-deployments/with-durable-objects/#durable-object-class-lifecycle-changes).
* **Rollbacks cannot cross a lifecycle change.** You cannot roll back to a version deployed before an `exports`\-driven lifecycle change. Refer to [Rollbacks - Bindings](https://sm-rtk-allowlist-page.previews.developers.cloudflare.com/workers/versions-and-deployments/rollbacks/#bindings).
* **Storage backends are immutable once provisioned.** You cannot change a namespace's `storage` value in place; delete and re-provision instead.

## Migrate from the legacy `migrations` flow

Existing Workers using the [migrations array](https://sm-rtk-allowlist-page.previews.developers.cloudflare.com/durable-objects/reference/durable-object-class-migrations-legacy/) can move to `exports` without any data migration. The provisioned namespaces remain in place; only the configuration shape changes.

To determine a class's existing storage backend, trace it to the migration that originally created it. Classes introduced through `new_sqlite_classes` use `sqlite`, while classes introduced through `new_classes` use `legacy-kv`.

1. Identify the live Durable Object classes your Worker exports today. Each class with an active namespace becomes a live `exports` entry.
2. Replace the `migrations` array with an `exports` map. For each existing class, add an entry keyed by the class name with `"type": "durable-object"`. Match the existing storage backend with `"storage": "sqlite"` or `"storage": "legacy-kv"`.
3. Remove the `migrations` array from your configuration.
4. Deploy.

Side-by-side, the equivalent of a typical `migrations` history is:

```jsonc
{
	"migrations": [
		{ "tag": "v1", "new_sqlite_classes": ["ChatRoom"] }
	]
}
```

```toml
[[migrations]]
tag = "v1"
new_sqlite_classes = [ "ChatRoom" ]
```

```jsonc
{
	"exports": {
		"ChatRoom": { "type": "durable-object", "storage": "sqlite" }
	}
}
```

```toml
[exports.ChatRoom]
type = "durable-object"
storage = "sqlite"
```

Future lifecycle changes (deletes, renames, transfers) happen entirely through `exports` — there is no equivalent of the `tag` field and no need to keep historical entries. The current state of your `exports` map is the source of truth.

Caution

Once a Worker has been deployed with `exports`, subsequent deploys cannot return to the legacy `migrations` array. Plan your transition accordingly.

Was this helpful?

YesNo

## On this page

[![](https://sm-rtk-allowlist-page.previews.developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://sm-rtk-allowlist-page.previews.developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/#page","headline":"Durable Object class exports · Cloudflare Durable Objects docs","description":"Use the declarative exports field in wrangler.json to manage Durable Object class lifecycle — create, delete, rename, and transfer Durable Object classes.","url":"https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-25","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```
