Skip to content

[wrangler] Add experimental support for declarative Durable Object exports#14210

Draft
petebacondarwin wants to merge 2 commits into
cloudflare:mainfrom
petebacondarwin:pbacondarwin/devx-2572-wrangler-do-exports
Draft

[wrangler] Add experimental support for declarative Durable Object exports#14210
petebacondarwin wants to merge 2 commits into
cloudflare:mainfrom
petebacondarwin:pbacondarwin/devx-2572-wrangler-do-exports

Conversation

@petebacondarwin

@petebacondarwin petebacondarwin commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Fixes DEVX-2572.

Adds client-side wiring for the EWC declarative Durable Object exports flow.

What this adds

A new declarative exports map in wrangler.json / wrangler.toml as an alternative to the legacy migrations array, gated behind the X_DO_EXPORTS environment variable:

{
	"exports": {
		"MyDO": { "type": "durable_object", "storage": "sqlite" },
		"OldGone": { "type": "durable_object", "state": "deleted" },
		"OldName": {
			"type": "durable_object",
			"state": "renamed",
			"renamed_to": "NewName"
		},
		"Movee": {
			"type": "durable_object",
			"state": "transferred",
			"transfer_to_script": "target-worker"
		},
		"Incoming": {
			"type": "durable_object",
			"state": "expecting_transfer",
			"storage": "sqlite",
			"transfer_from": "source-worker"
		}
	}
}

type carries the export kind (currently always "durable_object"); the new state field carries the lifecycle and defaults to "created" (live) when omitted. Tombstone states (deleted, renamed, transferred) retire / rename / transfer a provisioned namespace; expecting_transfer is a live state that names the receiving side of a two-phase cross-script transfer.

Both wrangler deploy and wrangler versions upload send the new payload when X_DO_EXPORTS=true is set:

X_DO_EXPORTS=true wrangler deploy

The upload response's exports_reconciliation envelope is rendered with the spec's visibility hierarchy:

  • errors → red per-class detail (rendered from the v4 envelope's meta.details[]), thrown as a UserError so every blocking scenario surfaces in one round trip
  • warnings → yellow, prominent (reserved — no current server scenarios emit one)
  • info → dim text, with per-entry blocked_by lists for stale tombstones
  • removable_entries → single-line "you can delete these from exports" hint

Local-dev support

Local development (wrangler dev and unstable_startWorker) now reads Durable Object SQLite storage settings from the new exports field, so applications using the declarative flow get correct local-dev storage without needing to also declare a migrations block. Live entries (state: "created" and state: "expecting_transfer") contribute the class to the local-dev DO map; tombstones do not.

The validator's "no lifecycle declared" warning is now exports-aware:

  • if the config already declares any exports entries (live or tombstone), the warning suggests extending the exports map.
  • if neither lifecycle is declared but X_DO_EXPORTS=true is set, the warning also suggests exports.
  • otherwise, the existing migrations warning fires.

The helper that drives this also got a more accurate rename: warnIfDurableObjectsHaveNoMigrationswarnIfDurableObjectsHaveNoLifecycleConfig.

@cloudflare/config integration

The new @cloudflare/config package's exports.durableObject() is extended with tombstone factories and an expectingTransfer() factory. The TS-facing API mirrors the wrangler/EWC structural shape with two stylistic transforms:

  • property names are camelCased (renamedTo, transferToScript, transferFrom)
  • enumeration values are kebab-cased (type: "durable-object", state: "expecting-transfer", storage: "legacy-kv")

convertExports performs the snake_case / underscore conversion at the wrangler boundary. InferDurableNamespaces<T> now correctly filters tombstones from binding-target inference, so a durableObject() typed binding can only reference a live class.

Fixture

fixtures/durable-objects-exports-app/ — a two-DO fixture that exercises the new shape end-to-end:

  • two live SQLite-backed Durable Objects (CounterA, CounterB)
  • a deleted tombstone (OldCounter)
  • a renamed tombstone (LegacyNameCounterB)
  • a diagnostic endpoint that runs ctx.storage.sql.exec("SELECT 1") to prove SQLite storage is honored in local dev via the new exports map

Tested with unstable_startWorker.

Server dependencies

Requires the exports_reconciliation account entitlement on Cloudflare's side. Hidden behind the X_DO_EXPORTS env var until that gate ships broadly.

Server-side phases: DEVX-2563 (1a), DEVX-2598 (1b), DEVX-2564 (2), DEVX-2600 (1d). DEVX-2599 (1c) adds full transferred support — until that lands, the transferred tombstone returns a "not yet implemented" error from EWC, which is rendered correctly via the same meta.details path.

Spec

https://wiki.cfdata.org/spaces/WX/pages/1396640001


  • Tests
    • Tests included/updated
    • Automated tests not possible - manual testing has been completed as follows:
    • Additional testing not necessary because:
  • Public documentation
    • Cloudflare docs PR(s):
    • Documentation not necessary because: customer-facing docs are tracked separately in DEVX-2569.

A picture of a cute animal (not mandatory, but encouraged)

@changeset-bot

changeset-bot Bot commented Jun 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 555ddf1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
wrangler Minor
@cloudflare/vite-plugin Patch
@cloudflare/vitest-pool-workers Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Jun 5, 2026

Copy link
Copy Markdown
create-cloudflare

npm i https://pkg.pr.new/create-cloudflare@14210

@cloudflare/deploy-helpers

npm i https://pkg.pr.new/@cloudflare/deploy-helpers@14210

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/@cloudflare/kv-asset-handler@14210

miniflare

npm i https://pkg.pr.new/miniflare@14210

@cloudflare/pages-shared

npm i https://pkg.pr.new/@cloudflare/pages-shared@14210

@cloudflare/unenv-preset

npm i https://pkg.pr.new/@cloudflare/unenv-preset@14210

@cloudflare/vite-plugin

npm i https://pkg.pr.new/@cloudflare/vite-plugin@14210

@cloudflare/vitest-pool-workers

npm i https://pkg.pr.new/@cloudflare/vitest-pool-workers@14210

@cloudflare/workers-auth

npm i https://pkg.pr.new/@cloudflare/workers-auth@14210

@cloudflare/workers-editor-shared

npm i https://pkg.pr.new/@cloudflare/workers-editor-shared@14210

@cloudflare/workers-utils

npm i https://pkg.pr.new/@cloudflare/workers-utils@14210

wrangler

npm i https://pkg.pr.new/wrangler@14210

@cloudflare/wrangler-bundler

npm i https://pkg.pr.new/@cloudflare/wrangler-bundler@14210

commit: be1facc

@petebacondarwin petebacondarwin force-pushed the pbacondarwin/devx-2572-wrangler-do-exports branch 9 times, most recently from bbc1df8 to 2755b93 Compare June 16, 2026 10:09
* Workflows are not yet supported on the export path; entries with
* non-Durable-Object types will be added here as those land.
*/
function convertExports(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function will be refactored when we add support for other "types" of exports that are not Durable Objects.

…ebab-case

Update the new declarative Durable Object `exports` config (still gated
behind `X_DO_EXPORTS`) to match the renamed EWC API surface:

- Property `transfer_to_script` -> `transfer_to` on the `transferred`
  tombstone variant.
- Enum values for `type`, `state`, and `storage` are now kebab-case on
  the wire: `durable_object` -> `durable-object`,
  `expecting_transfer` -> `expecting-transfer`,
  `legacy_kv` -> `legacy-kv`.
- Reconciliation response envelope fields renamed for symmetry:
  `to_script` -> `transfer_to`, `from_script` -> `transfer_from`.

Touches the config types in `@cloudflare/workers-utils`, the validator,
the `@cloudflare/config` converter (the kebab->snake `snakeStorage`
helper is now redundant and removed), the deploy-helpers reconciliation
renderer, the wrangler runtime consumers, the fixture, the E2E test, and
the changeset.
@petebacondarwin petebacondarwin force-pushed the pbacondarwin/devx-2572-wrangler-do-exports branch from 2755b93 to 555ddf1 Compare June 17, 2026 08:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Untriaged

Development

Successfully merging this pull request may close these issues.

2 participants