Skip to content
Cloudflare Docs

Development & testing

You can build, run, and test your Worker code on your own local 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.

By default, your Worker's bindings connect to locally simulated resources, but can be configured to interact with the real, production resource with remote bindings.

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).

Local development

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.

Defaults

By default, running wrangler dev / vite dev (when using the Vite plugin) means that:

  • Your Worker code runs on your local machine.
  • All resources your Worker is bound to in your Wrangler configuration are simulated locally.

Bindings during local development

Bindings are interfaces that allow your Worker to interact with various 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).

During local development, your Worker code interacts with these bindings using the exact same API calls (such as env.MY_KV.put()) as it would in a deployed environment. These local resources are initially empty, but you can populate them with data, as documented in Adding local data.

  • By default, bindings connect to local resource simulations (except for AI bindings, as AI models always run remotely).
  • You can override this default behavior and connect to the remote resource, on a per-binding basis. This lets you connect to real, production resources while still running your Worker code locally.

Remote bindings Beta

Remote bindings are bindings that are configured to connect to the deployed, remote resource during local development instead of the locally simulated resource. You can configure remote bindings by setting experimental_remote: true in the binding definition.

Example configuration

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

When remote bindings are configured, your Worker still executes locally, only the underlying resources your bindings connect to change. For all bindings marked with experimental_remote: true, Miniflare will route its operations (such as env.MY_KV.put()) to the deployed resource. All other bindings not explicitly configured with experimental_remote: true continue to use their default local simulations.

Using Wrangler with remote bindings

If you're using Wrangler for local development and have remote bindings configured, you'll need to use the following experimental command:

Terminal window
npx wrangler dev --x-remote-bindings

Using Vite with remote bindings

If you're using Vite via the Cloudflare Vite plugin, you'll need to add support for remote bindings in your Vite configuration (vite.config.ts):

vite.config.ts
import { cloudflare } from "@cloudflare/vite-plugin";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
cloudflare({
configPath: "./entry-worker/wrangler.jsonc",
experimental: { remoteBindings: true },
}),
],
});

Using Vitest with remote bindings

You can also use Vitest with configured remote bindings by enabling support in your Vitest configuration file (vitest.config.ts):

vitest.config.ts
import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config";
export default defineWorkersConfig({
test: {
poolOptions: {
workers: {
experimental_remoteBindings: true,
wrangler: { configPath: "./wrangler.jsonc" },
},
},
},
});

Targeting preview 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 ensure that remote bindings connect to that designated remote preview resource.

For example:

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

Running wrangler dev --x-remote-bindings 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
}
}

Workers for Platforms users can configure experimental_remote: true in dispatch namespace binding definitions:

{
"dispatch_namespaces": [
{
"binding": "DISPATCH_NAMESPACE",
"namespace": "testing",
"experimental_remote":true
}
]
}

This allows you to run your dynamic dispatch Worker locally, while connecting it to your remote dispatch namespace binding. This allows you to test changes to your core dispatching logic against real, deployed user Workers.

Unsupported remote bindings

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 for remote bindings.

  • Durable Objects: 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. 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.

API

Wrangler provides programmatic utilities to help tooling authors support remote binding connections when running Workers code with Miniflare.

Key APIs include:

experimental_startRemoteProxySession

This function starts a proxy 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.
  • remoteProxyConnectionString remoteProxyConnectionString: 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_startRemoteProxySession. It can be quite convenient to however read the binding declarations with unstable_readConfig and then pass them to experimental_startRemoteProxySession, 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_startRemoteProxySession.

experimental_maybeStartOrUpdateRemoteProxySession

This wrapper simplifies proxy session management. It takes:

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

It returns:

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

The function:

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

Example

Here's a basic example of using Miniflare with experimental_maybeStartOrUpdateRemoteProxySession to provide a local dev session with remote bindings. This example uses a single hardcoded KV binding.

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

wrangler dev --remote (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

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.

When using remote development, all bindings automatically connect to their remote resources. Unlike local development, 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 remote bindings 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.

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.