Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions src/components/cf/CompatibilityFlags.astro
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ const { experimental } = props.parse(Astro.props);

let flags = await getCollection("compatibility-flags");

if (experimental) {
flags = flags.filter((x) => x.data.experimental);
}
flags = flags.filter((x) => Boolean(x.data.experimental) === experimental);

flags.sort(
(a, b) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ dates because the compatibility date enables the same behavior.
This means all [Node.js built-in APIs](/workers/runtime-apis/nodejs/) supported
by the Workers runtime are available by default, including `node:crypto`,
`node:buffer`, `node:stream`, `node:net`, `node:dns`, `node:fs`, `node:http`,
and more. npm packages that depend on these APIs will work without additional
configuration.
and more. npm packages can use these APIs without additional runtime
configuration. Package compatibility still depends on the build tool and any
other APIs that the package uses.

Workers using an earlier compatibility date are not affected. They can still
opt in by adding `nodejs_compat` to `compatibility_flags`.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
title: Experimental module registry
description: Opt into URL-based module loading and runtime metadata.
products:
- workers
date: 2026-08-05
---

The experimental `new_module_registry` [compatibility flag](/workers/configuration/compatibility-flags/#new-module-registry) selects a URL-based module registry. It provides new module APIs and more consistent loading behavior.

Key changes include:

- **Module metadata** — Use `import.meta.url`, `import.meta.main`, and `import.meta.resolve()`.
- **URL identity** — Queries and fragments identify separate bundle module instances.
- **Module loading** — Validate import attributes and follow Node.js `require(esm)` behavior.
- **Consistent errors** — Use the same error classes across loading paths.
- **Lazy compilation** — Compile modules when they are first imported.
- **Compilation reuse** — Reuse cached compilation data when available.

Dynamic WebAssembly source phase imports through `import.source()` require the new registry. Static `import source` works with both registries.

Use [Wrangler](/workers/wrangler/) 4.87.0 or later. For Vite projects, use [Cloudflare Vite plugin](/workers/vite-plugin/) 1.35.0 or later.

For configuration, tooling caveats, and API details, refer to the [module registry reference](/workers/reference/module-registry/). For WebAssembly examples, refer to [Wasm in JavaScript](/workers/runtime-apis/webassembly/javascript/#use-source-phase-imports).
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,10 @@ enable_flag: "export_commonjs_default"
disable_flag: "export_commonjs_namespace"
---

CommonJS modules were previously exporting a module namespace (an object like `{ default: module.exports }`) rather than exporting only the `module.exports`. When this flag is enabled, the export is fixed.
The `export_commonjs_default` and `export_commonjs_namespace` flags apply only to the legacy module registry.

The legacy registry previously exported a module namespace like `{ default: module.exports }`. The `export_commonjs_default` flag exports `module.exports` directly.

The `export_commonjs_namespace` flag restores the previous namespace behavior.

Neither flag affects the new module registry. For its Node.js `require(esm)` behavior, refer to the [module registry reference](/workers/reference/module-registry/#use-require-with-es-modules).
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,10 @@ enable_flag: "disable_top_level_await_in_require"
disable_flag: "enable_top_level_await_in_require"
---

Workers implements the ability to use the Node.js style `require(...)` method
to import modules in the Worker bundle. Historically, this mechanism allowed
required modules to use top-level await. This, however, is not Node.js
compatible.
The `disable_top_level_await_in_require` and `enable_top_level_await_in_require` flags apply only to the legacy module registry.

The `disable_top_level_await_in_require` compat flag will cause `require()`
to fail if the module uses a top-level await. This flag is default enabled
with a compatibility date of 2024-12-02 or later.
The legacy registry originally allowed required modules to use top-level `await`. The `disable_top_level_await_in_require` flag makes `require()` fail when the required module uses top-level `await`.

To restore the original behavior allowing top-level await, use the
`enable_top_level_await_in_require` compatibility flag.
It is enabled by default for compatibility dates on or after `2024-12-02`. The `enable_top_level_await_in_require` flag restores the original behavior.

The [new module registry](/workers/reference/module-registry/#use-require-with-es-modules) always rejects `require()` when the target module or its import graph uses top-level `await`. Neither legacy flag changes this behavior.
31 changes: 31 additions & 0 deletions src/content/compatibility-flags/new-module-registry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
_build:
publishResources: false
render: never
list: never

name: "New module registry"
sort_date: "2026-08-05"
experimental: true
enable_flag: "new_module_registry"
disable_flag: "legacy_module_registry"
---

The experimental `new_module_registry` flag selects a URL-based Workers module registry. Use `legacy_module_registry` to select the legacy registry.

When enabled, the new registry provides:

- `import.meta` URL, path, entrypoint, and resolution metadata
- URL-based specifiers with query and fragment identities
- Import attribute validation and Node.js `require(esm)` behavior
- Consistent errors across module loading paths
- Compilation on first import and reuse of compilation data
- Dynamic WebAssembly source phase imports

Static `import source` works with both registries. Dynamic `import.source()` requires the new registry.

This flag has no default date. Add `new_module_registry` explicitly to opt in.

This flag only applies to JavaScript Workers. Python Workers ignore it and use the legacy registry.

For configuration and tooling requirements, refer to the [module registry reference](/workers/reference/module-registry/).
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,16 @@ enable_flag: "require_returns_default_export"
disable_flag: "require_returns_namespace"
---

When `require_returns_default_export` is enabled, `require()` will return the default export of a module if it exists. If the default export does not exist, it falls back to returning a mutable copy of the module namespace object.
The `require_returns_default_export` and `require_returns_namespace` flags apply only to the legacy module registry.

This matches the behavior that Node.js uses for `require(esm)`, where the default export is returned when available. This flag is useful for frameworks like Next.js that expect to be able to patch module exports.
With the legacy registry, `require_returns_default_export` makes `require()` return a module's default export when one exists. Otherwise, it returns a mutable copy of the module namespace object.

Previously, `require()` always returned the module namespace object (an object like `{default: module.exports}`).
The `require_returns_namespace` flag restores the previous namespace behavior.

Neither flag affects the [new module registry](/workers/reference/module-registry/#use-require-with-es-modules). The new registry follows Node.js `require(esm)` behavior and supports an additional compatibility marker for older bundler output.

It returns the module namespace object by default. A string-named `'module.exports'` export controls the returned value.

A truthy `__cjsUnwrapDefault` export returns the default export and takes precedence over those rules.

For [`workerd` `node:` built-ins](/workers/reference/module-registry/#load-nodejs-built-ins-consistently), `require()` returns the default export directly.
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@ enable_flag: "throw_on_unrecognized_import_assertion"
disable_flag: "ignore_unrecognized_import_assertion"
---

The `throw_on_unrecognized_import_assertion` flag controls how Workers handle
import attributes that are not recognized by the runtime. Previously, Workers
would ignore all import attributes, which is not compliant with the
specification. Runtimes are expected to throw an error when an import
attribute is encountered that is not recognized.
The `throw_on_unrecognized_import_assertion` and `ignore_unrecognized_import_assertion` flags apply only to the legacy module registry.

When the `ignore_unrecognized_import_assertion` flag is set, Workers will
ignore unrecognized import attributes.
The legacy registry previously ignored unrecognized import attributes. The `throw_on_unrecognized_import_assertion` flag rejects them instead.

The `ignore_unrecognized_import_assertion` flag restores the previous behavior.

The [new module registry](/workers/reference/module-registry/#validate-import-attributes) always validates import attributes. Neither legacy flag changes this behavior.
16 changes: 14 additions & 2 deletions src/content/docs/dynamic-workers/api-reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ You could, for example, use IDs of the form `<worker-name>:<version-number>`, wh

It is never guaranteed that two requests will go to the same isolate. Even if you use the same `WorkerStub` to make multiple requests, they could execute in different isolates. The callback passed to `loader.get()` could be called any number of times (although it is unusual for it to be called more than once).

Loader caching and module evaluation caching are separate. The loader can reuse code or a warm isolate by ID. Each isolate evaluates and caches its own module instances.

When local development enables the [new module registry](/workers/reference/module-registry/), compiled JavaScript and WebAssembly artifacts can be shared across isolate replicas. Module-level state remains local to each isolate.

### `WorkerCode`

This is the structure returned by `getCodeCallback` to represent a worker.
Expand All @@ -56,13 +60,21 @@ An optional list of [compatibility flags](/workers/configuration/compatibility-f

If true, then experimental compatibility flags will be permitted in `compatibilityFlags`. In order to set this, the worker calling the loader must itself have the compatibility flag `"experimental"` set. Experimental flags cannot be enabled in production.

The new module registry is experimental. Dynamic Workers therefore cannot use it in production.

#### <code>mainModule <Type text="string" /></code>

The name of the Worker's main module. This must be one of the modules listed in `modules`.
The runtime identifier for the Worker's main module. This must match one of the keys in `modules`.

#### <code>modules <Type text="Record<string, string | Module>"/></code>

A dictionary object mapping module names to their string contents. If the module content is a plain string, then the module name must have a file extension indicating its type: either `.js` or `.py`.
A dictionary object mapping runtime module identifiers to their contents. These identifiers name uploaded runtime modules, not original source files.

In local development with the [new module registry](/workers/reference/module-registry/), the identifier `index.js` maps to `file:///bundle/index.js`. Imports resolve against that registry, and URL specifiers do not fetch network resources.

Use ordinary module names unless the upload API documents support for URL-shaped names. These rules do not describe Python import resolution.

If the module content is a plain string, then the module name must have a file extension indicating its type: either `.js` or `.py`.

A module's content can also be specified as an object, in order to specify its type independent from the name. The allowed objects are:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ const response = await worker.getEntrypoint().fetch(request);

</TypeScriptExample>

In the playground, you can see this in action — run the same Dynamic Worker twice and the second request shows a cached result with 0ms cold start, since the build and load phases are skipped entirely.
In the playground, a cache hit can skip the build and load phases. Reuse is not guaranteed, so a later request can still create a new isolate or call the code callback again.

Reusing a Worker ID does not guarantee reuse of module-level state. Each isolate retains its own state.

## Observability with Tail Workers

Expand Down
12 changes: 7 additions & 5 deletions src/content/docs/dynamic-workers/getting-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ You can create a Worker that spins up other Workers, called Dynamic Workers, at

Dynamic Workers support two loading modes:

- `load(code)` creates a fresh Dynamic Worker for one-time execution.
- `get(id, callback)` caches a Dynamic Worker by ID so it can stay warm across requests.
- `load(code)` creates a fresh Dynamic Worker without an ID cache.
- `get(id, callback)` lets the runtime reuse code or a warm isolate by ID.

`load()` is best for one-time code execution, for example when using [Code Mode](/agents/tools/codemode/). `get(id, callback)` is better when the same code will receive subsequent requests, for example when you are building applications.
Use `load()` for one-time code execution, such as [Code Mode](/agents/tools/codemode/). Use `get()` when subsequent requests can use identical code. Reuse is not guaranteed.

### Try it out

Expand Down Expand Up @@ -99,11 +99,13 @@ In this example, `env.LOADER.load()` creates a Dynamic Worker from the code defi

`worker.getEntrypoint().fetch(request)` sends the incoming request to the Dynamic Worker's `fetch()` handler, which processes it and returns a response.

`mainModule` and the `modules` keys are runtime module identifiers. They do not represent original source paths. For import resolution details, refer to the [module registry reference](/workers/reference/module-registry/).

### Reusing a Dynamic Worker across requests

If you expect to load the exact same Worker more than once, use [`get(id, callback)`](/dynamic-workers/api-reference/#get) instead of `load()`. The `id` should be a unique string identifying the particular code you intend to load. When the runtime sees the same `id` again, it can reuse the existing Worker instead of creating a new one, if it hasn't been evicted yet.
If you expect to load identical code more than once, use [`get(id, callback)`](/dynamic-workers/api-reference/#get) instead of `load()`. The `id` should identify one exact code and configuration version. The runtime can reuse a warm Worker for that ID, but reuse is not guaranteed.

The callback you provide will only be called if the Worker is not already loaded. This lets you skip loading the code from storage when the Worker is already running.
The runtime calls your callback when it needs the Worker code. It can call the callback more than once, so return identical content for each ID.

<TypeScriptExample>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ If you're using the [Workers Script Upload API](/api/resources/workers/subresour
}
```

## Name modules

Each multipart file part name is a runtime module identifier. The `main_module` value selects one of those identifiers as the entrypoint. Module identifiers name uploaded runtime modules or output chunks, not original source files.

For JavaScript Workers using the [new module registry](/workers/reference/module-registry/), the identifier `main.js` maps to `file:///bundle/main.js`. Imports link against matching uploaded registry entries.

URL specifiers resolve through the registry and do not fetch network resources. Use ordinary module names unless the upload API documents support for URL-shaped names.

If your build emits multiple modules or chunks, upload every output as a separate multipart part. Preserve each generated name and content type so imports continue to resolve.

:::note

See examples of metadata being used with the Workers Script Upload API [here](/workers/platform/infrastructure-as-code#cloudflare-rest-api).
Expand Down
6 changes: 3 additions & 3 deletions src/content/docs/workers/languages/typescript/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ See [the `wrangler types` command docs](/workers/wrangler/commands/general/#type

This will generate a `d.ts` file and (by default) save it to `worker-configuration.d.ts`. This will include `Env` types based on your Worker bindings _and_ runtime types based on your Worker's compatibility date and flags.

You should then add that file to your `tsconfig.json`'s `compilerOptions.types` array. If you have the `nodejs_compat` compatibility flag, you should also install `@types/node`.
You should then add that file to your `tsconfig.json`'s `compilerOptions.types` array. If Node.js compatibility is enabled by your compatibility date or flags, you should also install `@types/node`.

You can commit your types file to git if you wish.

Expand Down Expand Up @@ -105,9 +105,9 @@ You can now remove any imports from `@cloudflare/workers-types` in your Worker c

Note that if you have specified a custom path for the runtime types file, you should use that in your `compilerOptions.types` array instead of the default path.

#### 4. Add @types/node if you are using [`nodejs_compat`](/workers/runtime-apis/nodejs/) (Optional)
#### 4. Add @types/node if you use [Node.js compatibility](/workers/runtime-apis/nodejs/) (Optional)

If you are using the `nodejs_compat` compatibility flag, you should also install `@types/node`.
If Node.js compatibility is enabled by your compatibility date or flags, install `@types/node`.

<PackageManagers type="add" pkg="@types/node" />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ Choose based on the build tools your project uses. You can also use the Vite plu
| Multi-Worker development | Supported | Supported |
| Frontend and server-side rendering frameworks | Use the framework build output | Integrates with Vite-powered frameworks |
| Build pipeline | Uses Wrangler's bundler or a custom build | Uses Vite transformations, Hot Module Replacement, and plugins |
| Default JavaScript output | One entry module | One entry chunk plus any generated chunks |
| Installed npm packages | Resolution handled by `esbuild` | Resolution handled by Vite |
| Deployment and resource management | Supported | Use Wrangler after `vite build` |
| [Rust Workers](/workers/languages/rust/) | Supported | Not supported |
| [Python Workers](/workers/languages/python/) | Use [`pywrangler`](/workers/languages/python/) instead of `wrangler` | Not supported |
Expand All @@ -33,6 +35,12 @@ Use the [Cloudflare Vite plugin](/workers/vite-plugin/) when your project alread

Use [`wrangler dev`](/workers/wrangler/commands/general/#dev) when your project does not use Vite or you want a direct command-line workflow. Wrangler also provides deployment and resource management commands.

Both tools handle installed npm package resolution during a build. Resolution and runtime support vary by package.

The Vite plugin supports Vite 6.1, 7, and 8. Vite 8 uses [Rolldown](https://rolldown.rs/), which can emit multiple output chunks. The plugin deploys these chunks as separate runtime modules through generated `no_bundle` configuration.

The Workers [module registry](/workers/reference/module-registry/) loads deployed runtime modules. Its features do not require Vite and apply to modules produced by other build tools.

For local development that requires deployed resources, both tools support [remote bindings](/workers/local-development/#remote-bindings). Your Worker runs locally while selected bindings connect to deployed Cloudflare resources.

For configuration differences when moving an existing project, refer to [Migrating from wrangler dev](/workers/vite-plugin/reference/migrating-from-wrangler-dev/).
Loading