---
title: Get Started
description: Install and configure the Miniflare API to dispatch events and test Cloudflare Workers locally.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/workers/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Get Started

The Miniflare API allows you to dispatch events to workers without making actual HTTP requests, simulate connections between Workers, and interact with local emulations of storage products like [KV](https://developers.cloudflare.com/workers/testing/miniflare/storage/kv), [R2](https://developers.cloudflare.com/workers/testing/miniflare/storage/r2), and [Durable Objects](https://developers.cloudflare.com/workers/testing/miniflare/storage/durable-objects). This makes it great for writing tests, or other advanced use cases where you need finer-grained control.

## Installation

Miniflare is installed using `npm` as a dev dependency:

 npm  yarn  pnpm  bun 

```
npm i -D miniflare
```

```
yarn add -D miniflare
```

```
pnpm add -D miniflare
```

```
bun add -d miniflare
```

## Usage

In all future examples, we'll assume Node.js is running in ES module mode. You can do this by setting the `type` field in your `package.json`:

package.json

```
{  ...  "type": "module"  ...}
```

To initialise Miniflare, import the `Miniflare` class from `miniflare`:

JavaScript

```
import { Miniflare } from "miniflare";
const mf = new Miniflare({  modules: true,  script: `  export default {    async fetch(request, env, ctx) {      return new Response("Hello Miniflare!");    }  }  `,});
const res = await mf.dispatchFetch("http://localhost:8787/");console.log(await res.text()); // Hello Miniflare!await mf.dispose();
```

The [rest of these docs](https://developers.cloudflare.com/workers/testing/miniflare/core/fetch) go into more detail on configuring specific features.

### String and File Scripts

Note in the above example we're specifying `script` as a string. We could've equally put the script in a file such as `worker.js`, then used the `scriptPath`property instead:

JavaScript

```
const mf = new Miniflare({  scriptPath: "worker.js",});
```

### Watching, Reloading and Disposing

Miniflare's API is primarily intended for testing use cases, where file watching isn't usually required. If you need to watch files, consider using a separate file watcher like [fs.watch() ↗](https://nodejs.org/api/fs.html#fswatchfilename-options-listener) or [chokidar ↗](https://github.com/paulmillr/chokidar), and calling setOptions() with your original configuration on change.

To cleanup and stop listening for requests, you should `dispose()` your instances:

JavaScript

```
await mf.dispose();
```

You can also manually reload scripts (main and Durable Objects') and options by calling `setOptions()` with the original configuration object.

### Updating Options and the Global Scope

You can use the `setOptions` method to update the options of an existing `Miniflare` instance. This accepts the same options object as the `new Miniflare` constructor, applies those options, then reloads the worker.

JavaScript

```
const mf = new Miniflare({  script: "...",  kvNamespaces: ["TEST_NAMESPACE"],  bindings: { KEY: "value1" },});
await mf.setOptions({  script: "...",  kvNamespaces: ["TEST_NAMESPACE"],  bindings: { KEY: "value2" },});
```

### Dispatching Events

`getWorker` dispatches `fetch`, `queues`, and `scheduled` events to workers respectively:

JavaScript

```
import { Miniflare } from "miniflare";
const mf = new Miniflare({  modules: true,  script: `  let lastScheduledController;  let lastQueueBatch;  export default {    async fetch(request, env, ctx) {      const { pathname } = new URL(request.url);      if (pathname === "/scheduled") {        return Response.json({          scheduledTime: lastScheduledController?.scheduledTime,          cron: lastScheduledController?.cron,        });      } else if (pathname === "/queue") {        return Response.json({          queue: lastQueueBatch.queue,          messages: lastQueueBatch.messages.map((message) => ({          id: message.id,          timestamp: message.timestamp.getTime(),          body: message.body,          bodyType: message.body.constructor.name,          })),        });      } else if (pathname === "/get-url") {        return new Response(request.url);      } else {        return new Response(null, { status: 404 });      }    },    async scheduled(controller, env, ctx) {      lastScheduledController = controller;      if (controller.cron === "* * * * *") controller.noRetry();    },    async queue(batch, env, ctx) {      lastQueueBatch = batch;      if (batch.queue === "needy") batch.retryAll();      for (const message of batch.messages) {        if (message.id === "perfect") message.ack();      }    }  }`,});
const res = await mf.dispatchFetch("http://localhost:8787/", {  headers: { "X-Message": "Hello Miniflare!" },});console.log(await res.text()); // Hello Miniflare!
const worker = await mf.getWorker();
const scheduledResult = await worker.scheduled({  cron: "* * * * *",});console.log(scheduledResult); // { outcome: "ok", noRetry: true });
const queueResult = await worker.queue("needy", [  { id: "a", timestamp: new Date(1000), body: "a", attempts: 1 },  { id: "b", timestamp: new Date(2000), body: { b: 1 }, attempts: 1 },]);console.log(queueResult); // { outcome: "ok", retryAll: true, ackAll: false, explicitRetries: [], explicitAcks: []}
```

See [📨 Fetch Events](https://developers.cloudflare.com/workers/testing/miniflare/core/fetch) and [⏰ Scheduled Events](https://developers.cloudflare.com/workers/testing/miniflare/core/scheduled)for more details.

### HTTP Server

Miniflare starts an HTTP server automatically. To wait for it to be ready, `await` the `ready` property:

JavaScript

```
import { Miniflare } from "miniflare";
const mf = new Miniflare({  modules: true,  script: `  export default {    async fetch(request, env, ctx) {      return new Response("Hello Miniflare!");    })  }  `,  port: 5000,});await mf.ready;console.log("Listening on :5000");
```

#### `Request#cf` Object

By default, Miniflare will fetch the `Request#cf` object from a trusted Cloudflare endpoint and cache it to `node_modules/.mf/cf.json`. You can disable this behaviour, using the `cf` option:

JavaScript

```
const mf = new Miniflare({  cf: false,});
```

You can also provide a custom cf object via a filepath:

JavaScript

```
const mf = new Miniflare({  cf: "cf.json",});
```

You can also control this behaviour using [system environment variables](https://developers.cloudflare.com/workers/wrangler/system-environment-variables/), which is useful when you are not using the Miniflare API directly (for example, when running `wrangler dev`):

Terminal window

```
# Disable cf fetching entirely (uses fallback data)export CLOUDFLARE_CF_FETCH_ENABLED=falsenpx wrangler dev
# Use a custom cache location for cf.jsonexport CLOUDFLARE_CF_FETCH_PATH=/tmp/.cf-cache.jsonnpx wrangler dev
```

The explicit `cf` option in the Miniflare API takes precedence over both environment variables.

### HTTPS Server

To start an HTTPS server instead, set the `https` option. To use the [default shared self-signed certificate ↗](https://github.com/cloudflare/workers-sdk/tree/main/packages/miniflare/src/http/cert.ts), set `https` to `true`:

JavaScript

```
const mf = new Miniflare({  https: true,});
```

To load an existing certificate from the file system:

JavaScript

```
const mf = new Miniflare({  // These are all optional, you don't need to include them all  httpsKeyPath: "./key.pem",  httpsCertPath: "./cert.pem",});
```

To load an existing certificate from strings instead:

JavaScript

```
const mf = new Miniflare({  // These are all optional, you don't need to include them all  httpsKey: "-----BEGIN RSA PRIVATE KEY-----...",  httpsCert: "-----BEGIN CERTIFICATE-----...",});
```

If both a string and path are specified for an option (e.g. `httpsKey` and `httpsKeyPath`), the string will be preferred.

### Logging

By default, `[mf:*]` logs are disabled when using the API. To enable these, set the `log` property to an instance of the `Log` class. Its only parameter is a log level indicating which messages should be logged:

JavaScript

```
import { Miniflare, Log, LogLevel } from "miniflare";
const mf = new Miniflare({  scriptPath: "worker.js",  log: new Log(LogLevel.DEBUG), // Enable debug messages});
```

## Reference

JavaScript

```
import { Miniflare, Log, LogLevel } from "miniflare";
const mf = new Miniflare({  // All options are optional, but one of script or scriptPath is required
  log: new Log(LogLevel.INFO), // Logger Miniflare uses for debugging
  script: `    export default {      async fetch(request, env, ctx) {        return new Response("Hello Miniflare!");      }    }  `,  scriptPath: "./index.js",
  modules: true, // Enable modules  modulesRules: [    // Modules import rule    { type: "ESModule", include: ["**/*.js"], fallthrough: true },    { type: "Text", include: ["**/*.text"] },  ],  compatibilityDate: "2021-11-23", // Opt into backwards-incompatible changes from  compatibilityFlags: ["formdata_parser_supports_files"], // Control specific backwards-incompatible changes  upstream: "https://miniflare.dev", // URL of upstream origin  workers: [{    // reference additional named workers    name: "worker2",    kvNamespaces: { COUNTS: "counts" },    serviceBindings: {      INCREMENTER: "incrementer",      // Service bindings can also be defined as custom functions, with access      // to anything defined outside Miniflare.      async CUSTOM(request) {        // `request` is the incoming `Request` object.        return new Response(message);      },    },    modules: true,    script: `export default {        async fetch(request, env, ctx) {          // Get the message defined outside          const response = await env.CUSTOM.fetch("http://host/");          const message = await response.text();
          // Increment the count 3 times          await env.INCREMENTER.fetch("http://host/");          await env.INCREMENTER.fetch("http://host/");          await env.INCREMENTER.fetch("http://host/");          const count = await env.COUNTS.get("count");
          return new Response(message + count);        }      }`,    },  }],  name: "worker", // Name of service  routes: ["*site.mf/worker"],

  host: "127.0.0.1", // Host for HTTP(S) server to listen on  port: 8787, // Port for HTTP(S) server to listen on  https: true, // Enable self-signed HTTPS (with optional cert path)  httpsKey: "-----BEGIN RSA PRIVATE KEY-----...",  httpsKeyPath: "./key.pem", // Path to PEM SSL key  httpsCert: "-----BEGIN CERTIFICATE-----...",  httpsCertPath: "./cert.pem", // Path to PEM SSL cert chain  cf: "./node_modules/.mf/cf.json", // Path for cached Request cf object from Cloudflare  liveReload: true, // Reload HTML pages whenever worker is reloaded


  kvNamespaces: ["TEST_NAMESPACE"], // KV namespace to bind  kvPersist: "./kv-data", // Persist KV data (to optional path)
  r2Buckets: ["BUCKET"], // R2 bucket to bind  r2Persist: "./r2-data", // Persist R2 data (to optional path)
  durableObjects: {    // Durable Object to bind    TEST_OBJECT: "TestObject", // className    API_OBJECT: { className: "ApiObject", scriptName: "api" },  },  durableObjectsPersist: "./durable-objects-data", // Persist Durable Object data (to optional path)
  cache: false, // Enable default/named caches (enabled by default)  cachePersist: "./cache-data", // Persist cached data (to optional path)  cacheWarnUsage: true, // Warn on cache usage, for workers.dev subdomains
  sitePath: "./site", // Path to serve Workers Site files from  siteInclude: ["**/*.html", "**/*.css", "**/*.js"], // Glob pattern of site files to serve  siteExclude: ["node_modules"], // Glob pattern of site files not to serve

  bindings: { SECRET: "sssh" }, // Binds variable/secret to environment  wasmBindings: { ADD_MODULE: "./add.wasm" }, // WASM module to bind  textBlobBindings: { TEXT: "./text.txt" }, // Text blob to bind  dataBlobBindings: { DATA: "./data.bin" }, // Data blob to bind});
await mf.setOptions({ kvNamespaces: ["TEST_NAMESPACE2"] }); // Apply options and reload
const bindings = await mf.getBindings(); // Get bindings (KV/Durable Object namespaces, variables, etc)
// Dispatch "fetch" event to workerconst res = await mf.dispatchFetch("http://localhost:8787/", {  headers: { Authorization: "Bearer ..." },});const text = await res.text();
const worker = await mf.getWorker();
// Dispatch "scheduled" event to workerconst scheduledResult = await worker.scheduled({ cron: "30 * * * *" })
const TEST_NAMESPACE = await mf.getKVNamespace("TEST_NAMESPACE");
const BUCKET = await mf.getR2Bucket("BUCKET");
const caches = await mf.getCaches(); // Get global `CacheStorage` instanceconst defaultCache = caches.default;const namedCache = await caches.open("name");
// Get Durable Object namespace and storage for IDconst TEST_OBJECT = await mf.getDurableObjectNamespace("TEST_OBJECT");const id = TEST_OBJECT.newUniqueId();const storage = await mf.getDurableObjectStorage(id);
// Get Queue Producerconst producer = await mf.getQueueProducer("QUEUE_BINDING");
// Get D1 Databaseconst db = await mf.getD1Database("D1_BINDING")
await mf.dispose(); // Cleanup storage database connections and watcher
```

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/workers/testing/miniflare/get-started/#page","headline":"Get Started · Cloudflare Workers docs","description":"Install and configure the Miniflare API to dispatch events and test Cloudflare Workers locally.","url":"https://developers.cloudflare.com/workers/testing/miniflare/get-started/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-28","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/workers/","name":"Workers"}},{"@type":"ListItem","position":3,"item":{"@id":"/workers/testing/","name":"Testing"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/testing/miniflare/","name":"Miniflare"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/testing/miniflare/get-started/","name":"Get Started"}}]}
```
