Skip to content
Cloudflare Docs

Development & testing

Cloudflare Workers offers three distinct development modes to build, run, and test your Worker code before deploying to production. Choose from fully local development with simulated resources, hybrid development that combines local execution with remote resource connections, or legacy remote development that runs entirely on Cloudflare's infrastructure.

Core concepts

Worker execution vs Bindings

When developing Workers, it's important to understand two distinct concepts:

  • Worker execution: Where your Worker code actually runs (on your local machine vs on Cloudflare's infrastructure).

  • Bindings: How your Worker interacts with Cloudflare resources (like KV namespaces, R2 buckets, D1 databases, Queues, Durable Objects, etc). In your Worker code, these are accessed via the env object (such as env.MY_KV).

While these are independent concepts, where your Worker runs and how bindings connect determines which development mode you're using.

Development Modes

Based on where your Worker runs and how bindings connect, there are three development modes:

Local development

Worker execution: Runs locally on your machine via wrangler dev / vite dev.

Bindings: Connect to local resource simulations by default.

Learn more.

Hybrid development

Worker execution: Runs locally on your machine, via the experimental wrangler dev --x-hybrid-dev command.

Bindings: Can connect to local resource simulations (default) or be configured to connect to the deployed, remote resource.

Learn more.

Remote development (Legacy)

Worker execution: Runs on Cloudflare's infrastructure via wrangler dev --remote.

Bindings: Always connect to remote resources.

Learn more.

Local development

Local development allows you to build, run, and test your Worker code on your own machine before deploying it to Cloudflare's network. This is made possible through Miniflare, a simulator that executes your Worker code using the same runtime used in production, workerd.

You can start a local development server using:

  1. The Cloudflare Workers CLI Wrangler, using the built-in wrangler dev command.
Terminal window
npx wrangler dev
  1. Vite, using the Cloudflare Vite plugin.
Terminal window
npx vite dev

Both Wrangler and the Cloudflare Vite plugin use Miniflare under the hood, and are developed and maintained by the Cloudflare team. For guidance on choosing when to use Wrangler versus Vite, see our guide Choosing between Wrangler & Vite.

Worker execution

During local development, your Worker code always runs on your local machine.

Bindings

When you start a local development server, Miniflare creates local simulations of Cloudflare resources, allowing your Worker code to interact with bindings without any code changes. Your Worker uses the exact same API calls (such as env.MY_KV.put()) as it would in a deployed environment, and Miniflare intercepts these calls and directs them to the locally simulated resource.

These local resources are initially empty, but you can populate them with data, as documented in Adding local data. If you want to connect to remote resources (such as a real, deployed R2 bucket) while still executing your Worker code locally, see hybrid development.

Hybrid development Beta

With hybrid development, your Worker executes locally and bindings are configured to connect to their remote counterparts (rather than their local simulations).

To run a hybrid development session:

  1. Configure bindings: Add the experimental_remote: true property to the binding definition. See all supported bindings in hybrid development.
  2. Run beta command: Use wrangler dev --x-hybrid-dev to start a hybrid development session.

Example configuration

{
"name": "my-worker",
"compatibility_date": "2025-06-01",
"browser": {
"binding": "MY_BROWSER",
"experimental_remote": true,
},
"queues": {
"producers": [
{
"queue": "queues-web-crawler",
"binding": "queues_web_crawler",
},
],
},
"r2_buckets": [
{
"bucket_name": "screenshots-bucket",
"binding": "screenshots_bucket",
"preview_bucket_name": "preview-screenshots-bucket",
"experimental_remote": true,
},
],
}

In the above example, you’re able to more realistically test out and trust that any Browser Rendering interactions work as intended, as well as validate the end results of the screenshots in R2 – while leaving room to rapidly iterate on the actual logic within your Worker and queues.

Worker execution

During hybrid development, your Worker code always runs on your local machine.

Bindings

For all bindings marked with experimental_remote: true, Miniflare routes its operations (such as env.MY_KV.put()) to the deployed resource. This connection is facilitated through a secure proxy mechanism provisioned on Cloudflare's network. All other bindings not explicitly configured with experimental_remote: true continue to use their default local simulations. Your Worker code can remain unchanged, regardless of whether the binding connects locally or remotely.

Targeting Preview vs. Production resources

To protect production data, you can create and specify preview resources in your Wrangler configuration, such as:

If preview configuration is present for a binding, setting experimental_remote: true will make your local session connect to that designated remote preview resource. If no preview configuration is specified, experimental_remote: true will connect to the main remote production resource.

For example:

{
"name": "my-worker",
"compatibility_date": "2025-06-01",
"r2_buckets": [
{
"bucket_name": "screenshots-bucket",
"binding": "screenshots_bucket",
"preview_bucket_name": "preview-screenshots-bucket",
"experimental_remote": true,
},
],
}

Running your local dev command wrangler dev --x-hybrid-dev with the above configuration means that:

  • Your Worker code runs locally
  • All calls made to env.screenshots_bucket will use the preview-screenshots-bucket resource, rather than the production screenshots-bucket.

We recommend configuring specific bindings to connect to their remote counterparts. These services often rely on Cloudflare's network infrastructure or have complex backends that are not fully simulated locally.

The following bindings are recommended to have experimental_remote: true in your Wrangler configuration:

To interact with a real headless browser for rendering. There is no current local simulation for Browser Rendering.

{
"browser": {
"binding": "MY_BROWSER",
"experimental_remote": true
},
}

To utilize actual AI models deployed on Cloudflare's network for inference. There is no current local simulation for Workers AI.

{
"ai": {
"binding": "AI",
"experimental_remote": true
},
}

To connect to your production Vectorize indexes for accurate vector search and similarity operations. There is no current local simulation for Vectorize.

{
"vectorize": [
{
"binding": "MY_VECTORIZE_INDEX",
"index_name": "my-prod-index",
"experimental_remote": true
}
],
}

To verify that the certificate exchange and validation process work as expected. There is no current local simulation for mTLS bindings.

{
"mtls_certificates": [
{
"binding": "MY_CLIENT_CERT_FETCHER",
"certificate_id": "<YOUR_UPLOADED_CERT_ID>",
"experimental_remote": true
}
]
}

To connect to a high-fidelity version of the Images API, and verify that all transformations work as expected. Local simulation for Cloudflare Images is limited with only a subset of features.

{
"images": {
"binding": "IMAGES" ,
"experimental_remote": true
}
}

Unsupported remote bindings

While hybrid development allows many bindings to connect to remote resources, certain bindings are not supported for remote connections during local development (experimental_remote: true). These will always use local simulations or local values.

If experimental_remote: true is specified in Wrangler configuration for any of the following unsupported binding types, Cloudflare will issue an error. See all supported and unsupported bindings in hybrid development.

  • Durable Objects: Durable Objects have synchronous operational aspects in their API. The current hybrid development proxy mechanism primarily supports asynchronous operations. Enabling remote connections for Durable Objects may be supported in the future, but currently will always run locally.

  • Environment Variables (vars): Environment variables are intended to be distinct between local development and deployed environments (production, preview). They are easily configurable locally (such as in a .dev.vars file or directly in Wrangler configuration).

  • Secrets: Like environment variables, secrets are expected to have different values in local development versus deployed environments for security reasons. Use .dev.vars for local secret management.

  • Static Assets: Static assets are always served from your local disk during development for speed and direct feedback on changes.

  • Version Metadata: Since your Worker code is running locally, version metadata (like commit hash, version tags) associated with a specific deployed version is not applicable or accurate.

  • Analytics Engine: Local development sessions typically don't contribute data directly to production Analytics Engine.

  • Hyperdrive: This is being actively worked on, but is currently unsupported.

  • Rate Limiting: Local development sessions typically should not share or affect rate limits of your deployed Workers. Rate limiting logic should be tested against local simulations.

Important Considerations

  • Data modification: Operations (writes, deletes, updates) on bindings connected remotely will affect your actual data in the targeted Cloudflare resource (be it preview or production).

  • Billing: Interactions with remote Cloudflare services through these connections will incur standard operational costs for those services (such as KV operations, R2 storage/operations, AI requests, D1 usage).

  • Network latency: Expect network latency for operations on these remotely connected bindings, as they involve communication over the internet.

  • Environment Variables & Secrets: Standard environment variables (from vars) and Secrets always use their locally defined values during local development. The experimental_remote: true flag does not apply to them.

API

Wrangler provides programmatic utilities to help tooling authors support hybrid development sessions when running Workers code with Miniflare.

Key APIs include:

experimental_startMixedModeSession

This function starts a hybrid development session for a given set of bindings. It accepts options to control session behavior, including an auth option with your Cloudflare account ID and API token for remote binding access.

It returns an object with:

  • ready Promise<void>: Resolves when the session is ready.
  • dispose () => Promise<void>: Stops the session.
  • updateBindings (bindings: StartDevWorkerInput['bindings']) => Promise<void>: Updates session bindings.
  • mixedModeConnectionString MixedModeConnectionString: String to pass to Miniflare for remote binding access.

unstable_convertConfigBindingsToStartWorkerBindings

The unstable_readConfig utility returns an Unstable_Config object which includes the definition of the bindings included in the configuration file. These bindings definitions are however not directly compatible with experimental_startMixedModeSession. It can be quite convenient to however read the binding declarations with unstable_readConfig and then pass them to experimental_startMixedModeSession, so for this wrangler exposes unstable_convertConfigBindingsToStartWorkerBindings which is a simple utility to convert the bindings in an Unstable_Config object into a structure that can be passed to experimental_startMixedModeSession.

experimental_maybeStartOrUpdateMixedModeSession

This wrapper simplifies hybrid session management. It takes:

  • The path to your Wrangler config, or an object with remote bindings.
  • The current hybrid session details (or null if none).

It returns:

  • null if no hybrid session is needed.
  • An object with the hybrid session details if started or updated.

The function:

  • based on the first argument prepares the input arguments for the hybrid session
  • if there are no remote bindings to be used (nor a pre-existing hybrid session) it returns null, signaling that no hybrid session is needed
  • if the details of an existing hybrid session have been provided it updates the hybrid session accordingly
  • otherwise if starts a new hybrid session
  • returns the hybrid session details (that can later be passed as the second argument to experimental_maybeStartOrUpdateMixedModeSession)

Example

Here's a basic example of using Miniflare with experimental_maybeStartOrUpdateMixedModeSession to provide a hybrid dev session. This example uses a single hardcoded KV binding.

import { Miniflare, MiniflareOptions } from "miniflare";
import { experimental_maybeStartOrUpdateMixedModeSession } from "wrangler";
let mf;
let mixedModeSessionDetails = null;
async function startOrUpdateDevSession() {
mixedModeSessionDetails =
await experimental_maybeStartOrUpdateMixedModeSession(
{
bindings: {
MY_KV: {
type: "kv_namespace",
id: "kv-id",
remote: true,
},
},
},
mixedModeSessionDetails,
);
const miniflareOptions = {
scriptPath: "./worker.js",
kvNamespaces: {
MY_KV: {
id: "kv-id",
mixedModeConnectionString:
mixedModeSessionDetails?.session.mixedModeConnectionString,
},
},
};
if (!mf) {
mf = new Miniflare(miniflareOptions);
} else {
mf.setOptions(miniflareOptions);
}
}
// ... tool logic that invokes `startOrUpdateDevSession()` ...
// ... once the dev session is no longer needed run
// `mixedModeSessionDetails?.session.dispose()`

Remote development (Legacy)

Separate from Miniflare-powered local development, Wrangler also offers a fully remote development mode via wrangler dev --remote. Remote development is not supported in the Vite plugin.

Terminal window
npx wrangler dev --remote

Worker execution

During remote development, all of your Worker code is uploaded to a temporary preview environment on Cloudflare's infrastructure, and changes to your code are automatically uploaded as you save.

Bindings

When using remote development, all bindings automatically connect to their remote resources. Unlike local and hybrid development modes, you cannot configure bindings to use local simulations - they will always use the deployed resources on Cloudflare's network.

When to use Remote development

  • For most development tasks, the most efficient and productive experience will be local development along with hybrid development when needed.
  • You may want to use wrangler dev --remote for testing features or behaviors that are highly specific to Cloudflare's network and cannot be adequately simulated locally or tested via remote bindings.

Considerations

  • Iteration is significantly slower than local development due to the upload/deployment step for each change.

Isolating from Production

To protect production data, you can specify preview resources in your Wrangler configuration, such as:

This separation ensures your development activities don't impact production data while still providing a realistic testing environment.

Limitations

  • When you run a remote development session using the --remote flag, a limit of 50 routes per zone is enforced. Learn more in Workers platform limits.