Example: Vitest with Workers bindings
This example shows how to test KV operations using @cloudflare/vitest-pool-workers with access to real bindings:
import { describe, it, expect } from "vitest";
import { env } from "cloudflare:workers";
describe("KV operations", () => {
it("should store and retrieve a value", async () => {
await env.MY_KV.put("key", "value");
const result = await env.MY_KV.get("key");
expect(result).toBe("value");
});
it("should return null for missing keys", async () => {
const result = await env.MY_KV.get("nonexistent");
expect(result).toBeNull();
});
});
Test with @cloudflare/vitest-pool-workers
The @cloudflare/vitest-pool-workers package runs your tests inside the Workers runtime, giving you access to real bindings (KV, R2, D1, Durable Objects) during tests. This catches issues that Node.js-based tests miss, like unsupported APIs or missing compatibility flags. One known pitfall: the Vitest pool automatically injects nodejs_compat, so tests pass even if your Wrangler configuration does not have the flag. Always confirm your wrangler.jsonc includes nodejs_compat if your code depends on Node.js built-in modules.
cf object not available in preview environment
The req.cf object is not available when running Workers in the preview environment. Code should check if req.cf is undefined before accessing it, and handle the case when it is unavailable.
Client IP, device type, and ASN not supported in playground
Getting client IP address, device type, and ASN are not supported in the Cloudflare Workers playground. These features must be tested on a live worker deployed to production.
Cron query parameter for testing
When testing Cron Triggers with wrangler dev using --test-scheduled, pass a cron query parameter to simulate different cron patterns in the HTTP request.
Cron Trigger testing with curl example
npx wrangler dev --test-scheduled
curl "http://localhost:8787/__scheduled?cron=0+*+*+*+*"
curl "http://localhost:8787/cdn-cgi/handler/scheduled?cron=*+*+*+*+*" # Python Workers
Cron Trigger route endpoint Python
Python Workers expose the /cdn-cgi/handler/scheduled route for testing Cron Triggers when using wrangler dev --test-scheduled, rather than the /__scheduled route used by JavaScript/TypeScript Workers.
Test cron triggers with wrangler dev
Test Cron Triggers locally using wrangler dev with the --test-scheduled flag. This exposes a /__scheduled route (or /cdn-cgi/handler/scheduled for Python Workers) that accepts HTTP requests to simulate cron execution.
Test specific cron pattern with wrangler
When testing cron triggers with wrangler dev --test-scheduled, pass a cron query parameter to simulate different schedules. Example: curl "http://localhost:8787/__scheduled?cron=*%2F3+*+*+*+*" tests the every-3-minutes pattern.
Remote bindings for local development
For each microfrontend you want to run remotely while in local dev, configure its service binding with the remote flag. Example configuration: {"services": [{"binding": "<BINDING_NAME>", "service": "<WORKER_NAME>", "remote": true}]}
Local development with microfrontends
During development, you can test your microfrontend architecture locally using Wrangler's service binding support. Run the router Worker locally using wrangler dev, and then in separate terminals run each of the microfrontends. If you only need to work on one microfrontend, you can run the others remotely using remote bindings without needing to have access to the source code or run a local dev server.
Local development command for React + Workers
Start the local development server by running: npm run dev
Cloudflare Vite plugin for local development
The Cloudflare Vite plugin in vite.config.ts runs the Worker in the Cloudflare Workers runtime during local development, making the local environment as close to production as possible. It enables local emulation of bindings and includes Vite features like hot module replacement (HMR).
Wrangler dev server starts local development
Run 'npx wrangler dev' in the project directory to start a local server for developing your Worker. This allows you to preview your Worker locally during development. The Worker is accessible at http://localhost:8787. Wrangler will open your web browser to login to your Cloudflare account if you have never used it before.
Local development with uv run pywrangler dev
When running uv run pywrangler dev for local development, the Workers runtime: (1) determines which version of Pyodide is required based on compatibility date, (2) installs packages from pyproject.toml, (3) creates a new V8 isolate and injects Pyodide, and (4) serves the Python code using Pyodide. No extra toolchain or precompilation steps are needed.
Clearing local storage
You can delete the .wrangler/state folder at any time to reset your local environment. Miniflare will recreate it the next time you run the dev command. You can also delete specific subdirectories within .wrangler/state for targeted clean-up.
Local resources created automatically by Miniflare
When you run wrangler dev or vite, Miniflare automatically creates local versions of resources like KV, D1, and R2. You do not need to manually set up separate local instances for each service.
Default local data storage location
Both Wrangler and the Vite plugin store local binding data in the .wrangler/state folder in your project directory by default. This folder contains subdirectories for all local bindings including KV namespaces, R2 buckets, D1 databases, and Durable Objects.
Changes to local resources do not affect production
Modifications made to local resources during development will not impact production data.
Local resources are empty by default
Newly created local resources will not contain any data. You must use Wrangler commands with the --local flag to populate them with data.
vite dev tunnel activation
To start a tunnel in the Cloudflare Vite plugin while running `vite dev`, press t + Enter to start or close the tunnel.
wrangler dev tunnel activation
To start a tunnel in Wrangler while running `wrangler dev`, press [t] to start or close the tunnel. Wrangler will print the public tunnel URL or URLs for the current session.
Vite preview Quick tunnel allowedHosts
When using `vite preview` with a Quick tunnel, add `.trycloudflare.com` to `preview.allowedHosts` in vite.config.ts to allow preview host validation.
Quick tunnel hostname
A Quick tunnel provides a random `*.trycloudflare.com` hostname for a single session without needing to create a named tunnel.
Vite plugin automatic tunnel
To have the tunnel open automatically when Vite starts, set `tunnel.autoStart` to `true` in the cloudflare plugin config.
Vite plugin named tunnel configuration
To use a named tunnel with the Cloudflare Vite plugin for stable hostnames, add `tunnel: { name: "my-tunnel" }` to the cloudflare plugin config in vite.config.ts.
Vite preview named tunnel allowedHosts
When using `vite preview` with a named tunnel, add the resolved hostnames or a matching domain suffix such as `.my-domain.com` to `preview.allowedHosts` in vite.config.ts to allow preview host validation.
wrangler dev automatic tunnel
To have the tunnel open automatically when Wrangler starts, use the `--tunnel` flag with the `wrangler dev` command.
wrangler dev named tunnel
To use a named tunnel with Wrangler, run `wrangler dev --tunnel-name=my-tunnel` to specify a stable hostname via a named tunnel.
Local Explorer requirements
Local Explorer requires Wrangler 4.118.0 or later, or Cloudflare Vite plugin 1.50.0 or later. Local Explorer is available by default and automatically detects bindings defined in Wrangler configuration.
Local Explorer use cases with AI agents
Local Explorer API is useful for agents to populate test data in local KV namespaces or D1 databases, inspect the state of a Durable Object during debugging, trigger or retry Workflow runs with different input data, upload test files to a local R2 bucket, and find recent requests with errors and drill into failing spans.
D1 and Durable Objects SQL Studio
Local Explorer includes a SQL Studio for D1 databases and Durable Objects using the SQLite storage API. This provides the same experience available in the Cloudflare dashboard for deployed D1 databases, with a visual table browser featuring inline editing and a SQL query editor for running arbitrary queries.
Local Explorer works with Wrangler and Vite plugin
Local Explorer works with both Wrangler and the Cloudflare Vite plugin for local development.
Local Explorer supported bindings and operations
Local Explorer supports the following bindings: KV (view and browse keys/values/metadata, create/update/delete key-value pairs), R2 (list objects/view metadata, upload/delete objects), D1 (browse tables/rows/run SQL queries, insert/update/delete rows through SQL), Durable Objects with SQLite storage (browse SQLite tables/rows/run SQL queries, insert/update/delete rows through SQL), and Workflows (list instances/view status and step history, trigger new runs/retry failed instances).
Local Explorer API for AI agents
When Wrangler or the Cloudflare Vite plugin detects it is running inside an AI agent, it prints a hint to the terminal with the Local Explorer API endpoint. The agent can fetch the OpenAPI specification from that endpoint to discover available operations, then make API calls to read or modify local data, query traces and logs, and debug the Worker. The API endpoint for querying traces and logs is POST /cdn-cgi/explorer/api/local/observability/query.
Local Explorer API endpoint
Local Explorer exposes an API at /cdn-cgi/explorer/api that provides programmatic access to the same operations available in the browser interface. The API serves an OpenAPI specification describing all available endpoints, parameters, and response formats. Retrieve the spec with: curl http://localhost:8787/cdn-cgi/explorer/api
Local Explorer access and opening
Local Explorer is a browser-based interface available at /cdn-cgi/explorer on your local development server. It is used for viewing and editing data in local bindings and debugging Worker invocations during development. To open Local Explorer with Wrangler, start a local development session with 'wrangler dev' and press 'e' in the terminal. With the Cloudflare Vite plugin, navigate directly to /cdn-cgi/explorer on your dev server's route and port.
Single dev command for multiple Workers with Vite plugin
Configure auxiliaryWorkers in your Vite configuration. Example: import { defineConfig } from 'vite'; import { cloudflare } from '@cloudflare/vite-plugin'; export default defineConfig({ plugins: [cloudflare({ configPath: './app/wrangler.jsonc', auxiliaryWorkers: [{ configPath: './api/wrangler.jsonc' }] })] }); Then run vite dev.
Single dev command for multiple Workers with Wrangler
Run multiple Workers in a single dev command by passing multiple configuration files to Wrangler's dev server: wrangler dev -c ./app/wrangler.jsonc -c ./api/wrangler.jsonc. The first config is treated as the primary Worker, exposed at http://localhost:8787. Additional configs run as auxiliary Workers, available via service bindings or tail consumers from the primary Worker. This approach is recommended as the default for most development workflows as it ensures the best compatibility with bindings.
When to use single dev command for multiple Workers
Use the single dev command approach when you want the simplest setup for development, Workers are part of the same application or codebase, you need to access a Durable Object namespace or Workflow from another Worker using script_name, or you need to set up Queues where the producer and consumer Workers are separated.
Hybrid approach for multiple dev setups
You can combine both approaches: run a group of Workers together through vite dev using auxiliaryWorkers, while running another Worker separately with wrangler dev. This allows you to keep tightly coupled Workers running under a single dev command, while keeping independent or shared Workers in separate ones.
When to use multiple dev commands
Use multiple dev commands when you want each Worker to be accessible on its own local URL during development since only the primary Worker is exposed when using a single dev command, each Worker has its own build setup or tooling, or you need the flexibility to run and develop Workers independently without restructuring your project or consolidating configs. This setup is especially useful in larger projects where each team maintains a subset of Workers.
Multiple dev commands for separate Workers
Run each Worker in separate dev commands, each with its own terminal and configuration: wrangler dev -c ./app/wrangler.jsonc in terminal 1 and wrangler dev -c ./api/wrangler.jsonc in terminal 2. These Workers run in different dev commands but can still communicate with each other via service bindings or tail consumers regardless of whether they are started with wrangler dev or vite dev.
Request/Response FormData Content-Type warning
As of 2020-04-16, preview logs a warning when attempting to construct Request or Response with FormData body but Content-Type header is overridden, as such bodies would not be parseable.
Playground HTTP testing panel
To test raw HTTP requests in the Playground, such as POST requests, go to the HTTP tab and select Send. Headers can be added and edited via this panel, and the request body can be edited as well.
Unit tests with Workers Vitest integration
Use the Workers Vitest integration for unit tests to get fast feedback while testing individual functions and modules. Tests run inside the Workers runtime, so test code can access bindings and runtime APIs directly. The integration provides direct assertions against binding state such as values written to KV, R2, D1, or Durable Objects, and enables direct calls to Durable Objects and other runtime APIs.
Recommended testing tools for Workers
For most projects, use the Workers Vitest integration for unit tests and the createTestHarness() API for integration tests. These are the complementary tools provided by the Workers platform for testing different parts of an application.
Integration tests with createTestHarness API
Use the createTestHarness() API to exercise one or more Workers as a whole and test how they interact with each other and with external services. The integration test harness provides confidence from exercising production Worker builds, coverage through configured HTTP routes across Workers, and compatibility with any Node.js test runner and tools such as Playwright or MSW.
Miniflare core testing features overview
Miniflare provides core features for testing Cloudflare Workers, including support for fetch events and compatibility settings. The core functionality is organized in a documentation directory structure under /core.
Miniflare upstream fallback behavior
If a fetch listener does not return a response, or throws an exception after calling passThroughOnException(), Miniflare will fetch the response from the configured upstream URL instead. When dispatching with dispatchFetch, if you use the same URL as the upstream, Miniflare will rewrite it to match the upstream.
Miniflare complete fetch event dispatch example
Example showing a complete Miniflare setup with dispatchFetch: const mf = new Miniflare({ modules: true, script: `export default { async fetch(request, env, ctx) { const body = JSON.stringify({ url: event.request.url, header: event.request.headers.get("X-Message"), }); return new Response(body, { headers: { "Content-Type": "application/json" }, }); }}` }); let res = await mf.dispatchFetch("http://localhost:8787/"); console.log(await res.json()); res = await mf.dispatchFetch("http://localhost:8787/1", { headers: { "X-Message": "1" } }); console.log(await res.json());
Miniflare HTTP request handling
When an HTTP request is made, a Request object is dispatched to the worker and the generated Response is returned. The Request object includes a cf object with Cloudflare-specific properties. Miniflare logs the method, path, status, and response time. If the worker throws an error, an error page with stack trace is returned instead.
dispatchFetch API signatures
The dispatchFetch function accepts two forms: dispatchFetch(url) or dispatchFetch(url, requestInit) where url is a string, or dispatchFetch(request) where request is a Request object. This API mirrors the standard fetch method.
Setting CF-* headers and cf object in Miniflare tests
When dispatching fetch events in Miniflare, you must manually add CF-* headers and the cf object to the request to control their values for testing. Example: await mf.dispatchFetch("http://localhost:8787", { headers: { "CF-IPCountry": "GB" }, cf: { country: "GB" } })
dispatchFetch example with Request object
Example showing dispatchFetch with a Request object: await mf.dispatchFetch(new Request("http://localhost:8787/2", { headers: { "X-Message": "2" } })) dispatches a fetch event using a Request instance.
dispatchFetch example with URL and headers
Example showing dispatchFetch dispatch with URL string and RequestInit object: await mf.dispatchFetch("http://localhost:8787/1", { headers: { "X-Message": "1" } }) dispatches a fetch event with custom headers.
Miniflare upstream example with passThroughOnException
Example showing upstream fallback: const mf = new Miniflare({ script: `addEventListener("fetch", (event) => { event.passThroughOnException(); throw new Error(); });`, upstream: "https://miniflare.dev" }); const res = await mf.dispatchFetch("https://miniflare.dev/core/fetch"); This will fetch from the upstream when the listener throws an error.
Miniflare Queue producers configuration
To add queue producers to a Miniflare environment, use the queueProducers option. You can specify producers as an object mapping binding names to queue names: const mf = new Miniflare({ queueProducers: { MY_QUEUE: "my-queue" } }). If the binding name and queue name are identical, you can use an array: queueProducers: ["MY_QUEUE"].
Miniflare Queue consumers configuration
To add queue consumers to a Miniflare environment, use the queueConsumers option with a queue name as key and a configuration object. The configuration object supports: maxBatchSize (default: 5), maxBatchTimeout in seconds (default: 1), maxRetries (default: 2), and deadLetterQueue (default: none). Alternatively, use an array with just the queue name to apply default consumer options: queueConsumers: ["my-queue"].
Miniflare Queue consumer configuration options
Queue consumers in Miniflare accept the following configuration: maxBatchSize (number, default 5) - maximum messages per batch; maxBatchTimeout (number in seconds, default 1) - time to wait before processing a partial batch; maxRetries (number, default 2) - maximum retry attempts for failed messages; deadLetterQueue (string, default none) - optional queue name for failed messages.