INTEGRITY Cloudflare Docs

Local development

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:

Start a local development server

You can start a local development server using:

  1. The Cloudflare Workers CLI Wrangler, using the built-in wrangler dev command.
npx wrangler dev
  1. Vite, using the Cloudflare Vite plugin.
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:

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.

Remote bindings

Remote bindings are bindings that are configured to connect to the deployed, remote resource during local development instead of the locally simulated resource. Remote bindings are supported by Wrangler, the Cloudflare Vite plugin, and the @cloudflare/vitest-plugin package. You can configure remote bindings by setting remote: true in the binding definition.

Example configuration

{
	"name": "my-worker",
	// Set this to today's date
	"compatibility_date": "2026-08-28",

	"r2_buckets": [
		{
			"bucket_name": "screenshots-bucket",
			"binding": "screenshots_bucket",
			"remote": true,
		},
	],
}
name = "my-worker"
# Set this to today's date
compatibility_date = "2026-08-28"

[[r2_buckets]]
bucket_name = "screenshots-bucket"
binding = "screenshots_bucket"
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 remote: true, Miniflare will route its operations (such as env.MY_KV.put()) to the deployed resource. All other bindings not explicitly configured with remote: true continue to use their default local simulations.

Integration with environments

Remote Bindings work well together with Workers Environments. To protect production data, you can create a development or staging environment and specify different resources in your Wrangler configuration than you would use for production.

For example:

{
	"name": "my-worker",
	// Set this to today's date
	"compatibility_date": "2026-08-28",

	"env": {
		"production": {
			"r2_buckets": [
				{
					"bucket_name": "screenshots-bucket",
					"binding": "screenshots_bucket",
				},
			],
		},
		"staging": {
			"r2_buckets": [
				{
					"bucket_name": "preview-screenshots-bucket",
					"binding": "screenshots_bucket",
					"remote": true,
				},
			],
		},
	},
}
name = "my-worker"
# Set this to today's date
compatibility_date = "2026-08-28"

[[env.production.r2_buckets]]
bucket_name = "screenshots-bucket"
binding = "screenshots_bucket"

[[env.staging.r2_buckets]]
bucket_name = "preview-screenshots-bucket"
binding = "screenshots_bucket"
remote = true

Running wrangler dev -e staging (or CLOUDFLARE_ENV=staging vite dev) with the above configuration means that:

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 remote: true in your Wrangler configuration:

Browser Run:

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

{
	"browser": {
		"binding": "MY_BROWSER",
		"remote": true
	},
}
[browser]
binding = "MY_BROWSER"
remote = true

Workers AI:

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

{
	"ai": {
		"binding": "AI",
		"remote": true
	},
}
[ai]
binding = "AI"
remote = true

Vectorize:

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",
			"remote": true
		}
	],
}
[[vectorize]]
binding = "MY_VECTORIZE_INDEX"
index_name = "my-prod-index"
remote = true

mTLS:

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>",
			"remote": true
			}
	]
}
[[mtls_certificates]]
binding = "MY_CLIENT_CERT_FETCHER"
certificate_id = "<YOUR_UPLOADED_CERT_ID>"
remote = true

Images:

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" ,
		"remote": true
	}
}
[images]
binding = "IMAGES"
remote = true

Dispatch Namespaces:

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

{
	"dispatch_namespaces": [
		{
			"binding": "DISPATCH_NAMESPACE",
			"namespace": "testing",
			"remote":true
		}
	]
}
[[dispatch_namespaces]]
binding = "DISPATCH_NAMESPACE"
namespace = "testing"
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 (i.e. with remote: true) during local development. These will always use local simulations or local values.

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

Using remote resources with Durable Objects and Workflows

While Durable Object and Workflow bindings cannot currently be remote, you can still use them during local development and have them interact with remote resources.

There are two recommended patterns for this:

Important Considerations

Connect to Access-protected Workers

If your Worker is protected by Cloudflare Access, Wrangler must authenticate with Access when connecting to your remote bindings. This applies whether Access protects the Worker itself, all Workers in the account, a workers.dev hostname, a Custom Domain, or another hostname or path that routes to the Worker.

There are two ways you can authenticate against Access:

To set up service token authentication:

  1. Create a service token.

    In the Cloudflare dashboard, go to Zero Trust > Access > Service Auth > Service Tokens and create a new token. Refer to Service tokens for the full reference. You will be shown a Client ID and a Client Secret — save them somewhere safe, as the secret is not shown again.

  2. Add a Service Auth policy to the Access application that protects your Worker.

    Open the existing Access application that already protects the Worker or the hostname you use for remote bindings, and attach a new policy with:

    • Action: Service Auth
    • Include: The service token you created, or "Any Access Service Token" if you want to allow any service token to access the Worker.
  3. Expose the credentials to Wrangler.

    Set the CLOUDFLARE_ACCESS_CLIENT_ID and CLOUDFLARE_ACCESS_CLIENT_SECRET system environment variables in the environment that runs Wrangler:

    export CLOUDFLARE_ACCESS_CLIENT_ID=<CLIENT_ID>
    export CLOUDFLARE_ACCESS_CLIENT_SECRET=<CLIENT_SECRET>

    In CI, store the values as secrets and expose them as environment variables to the step that runs Wrangler.

API

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

Key APIs include:

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:

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 startRemoteProxySession. It can be quite convenient to however read the binding declarations with unstable_readConfig and then pass them to 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 startRemoteProxySession.

maybeStartOrUpdateRemoteProxySession

This wrapper simplifies proxy session management. It takes:

It returns an object with the proxy session details if started or updated, or null if no proxy session is needed.

The function:

Example

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

import { Miniflare, MiniflareOptions } from "miniflare";
import { maybeStartOrUpdateRemoteProxySession } from "wrangler";

let mf;

let remoteProxySessionDetails = null;

async function startOrUpdateDevSession() {
	remoteProxySessionDetails = await maybeStartOrUpdateRemoteProxySession(
		{
			bindings: {
				MY_KV: {
					type: "kv_namespace",
					id: "kv-id",
					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()`
import { Miniflare, MiniflareOptions } from "miniflare";
import { maybeStartOrUpdateRemoteProxySession } from "wrangler";

let mf: Miniflare | null;

let remoteProxySessionDetails: Awaited<
	ReturnType<typeof maybeStartOrUpdateRemoteProxySession>
> | null = null;

async function startOrUpdateDevSession() {
	remoteProxySessionDetails = await maybeStartOrUpdateRemoteProxySession(
		{
			bindings: {
				MY_KV: {
					type: "kv_namespace",
					id: "kv-id",
					remote: true,
				},
			},
		},
		remoteProxySessionDetails,
	);

	const miniflareOptions: 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.

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

Considerations

Limitations