Local development
Worker execution: Runs locally on your machine via wrangler dev / vite dev.
Bindings: Connect to local resource simulations by default.
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.
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.
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.
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.
Remote development (Legacy)
Worker execution: Runs on Cloudflare's infrastructure via wrangler dev --remote.
Bindings: Always connect to remote resources.
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:
wrangler dev command.npx wrangler devyarn wrangler devpnpm wrangler devnpx vite devyarn vite devpnpm vite devBoth 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.
During local development, your Worker code always runs on your local machine.
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.
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:
experimental_remote: true property to the binding definition. See all supported bindings in hybrid development.wrangler dev --x-hybrid-dev to start a hybrid development session.{ "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, }, ],}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 = trueIn 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.
During hybrid development, your Worker code always runs on your local machine.
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.
To protect production data, you can create and specify preview resources in your Wrangler configuration, such as:
preview_id.preview_bucket_name.preview_database_idIf 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, }, ],}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 = trueRunning your local dev command wrangler dev --x-hybrid-dev with the above configuration means that:
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 },}[browser]binding = "MY_BROWSER"experimental_remote = trueTo 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 },}[ai]binding = "AI"experimental_remote = trueTo 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 } ],}[[vectorize]]binding = "MY_VECTORIZE_INDEX"index_name = "my-prod-index"experimental_remote = trueTo 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
} ]
}[[mtls_certificates]]binding = "MY_CLIENT_CERT_FETCHER"certificate_id = "<YOUR_UPLOADED_CERT_ID>"experimental_remote = trueTo 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 }}[images]binding = "IMAGES"experimental_remote = trueWhile 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.
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.
Wrangler provides programmatic utilities to help tooling authors support hybrid development sessions when running Workers code with Miniflare.
Key APIs include:
experimental_startMixedModeSession - starts a hybrid development session that allows interaction with remote bindingsunstable_convertConfigBindingsToStartWorkerBindings - utility for converting binding definitionsexperimental_maybeStartOrUpdateMixedModeSession - convenience function to easily start or update a hybrid sessionThis 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.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.
This wrapper simplifies hybrid session management. It takes:
null if none).It returns:
null if no hybrid session is needed.The function:
experimental_maybeStartOrUpdateMixedModeSession)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()`import { Miniflare, MiniflareOptions } from "miniflare";import { experimental_maybeStartOrUpdateMixedModeSession } from "wrangler";
let mf: Miniflare | null;
let mixedModeSessionDetails: Awaited< ReturnType<typeof experimental_maybeStartOrUpdateMixedModeSession>> | null = null;
async function startOrUpdateDevSession() { mixedModeSessionDetails = await experimental_maybeStartOrUpdateMixedModeSession( { bindings: { MY_KV: { type: 'kv_namespace', id: 'kv-id', remote: true, } } }, mixedModeSessionDetails );
const miniflareOptions: 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()`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.
npx wrangler dev --remoteyarn wrangler dev --remotepnpm wrangler dev --remoteDuring 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.
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.
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.To protect production data, you can specify preview resources in your Wrangler configuration, such as:
preview_id.
wrangler dev --remote.preview_bucket_name.preview_database_idThis separation ensures your development activities don't impact production data while still providing a realistic testing environment.
--remote flag, a limit of 50 routes per zone is enforced. Learn more in Workers platform limits.