Skip to main content
Miniflare is a local simulator for Cloudflare Workers, powered by the same workerd runtime used in production. It provides a full-featured development environment with support for all Cloudflare bindings.

Installation

npm install miniflare

Basic Usage

import { Miniflare } from "miniflare";

const mf = new Miniflare({
  script: `
    export default {
      async fetch(request, env) {
        return new Response("Hello from Miniflare!");
      }
    }
  `,
  modules: true,
});

const response = await mf.dispatchFetch("http://localhost");
console.log(await response.text());

await mf.dispose();

Constructor

new Miniflare(options)

Creates a new Miniflare instance.
options
MiniflareOptions
required
Configuration options

Instance Methods

dispatchFetch()

Send an HTTP request to the worker.
const response = await mf.dispatchFetch("http://localhost/api", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ key: "value" }),
});
input
RequestInfo
required
URL or Request object
init
RequestInit
Request initialization options
response
Promise<Response>
HTTP response from the worker

getWorker()

Get a worker instance by name.
const worker = await mf.getWorker("my-worker");

// Dispatch fetch
await worker.fetch("http://localhost");

// Trigger scheduled event
await worker.scheduled({ cron: "0 0 * * *" });

// Send queue messages
await worker.queue("my-queue", [
  { id: "1", timestamp: Date.now(), body: "message" },
]);
name
string
Worker name (optional for single-worker setups)
worker
Promise<MiniflareWorker>
Worker instance with methods:

getBindings()

Get all bindings for a worker.
const bindings = await mf.getBindings();

// Access KV
await bindings.MY_KV.put("key", "value");
const value = await bindings.MY_KV.get("key");

// Access D1
const result = await bindings.DB.prepare("SELECT * FROM users").all();

// Access R2
await bindings.MY_BUCKET.put("file.txt", "contents");
name
string
Worker name (optional for single-worker setups)
bindings
Promise<Bindings>
Object containing all worker bindings (KV, D1, R2, DO, etc.)

getCf()

Get the simulated request.cf object.
const cf = await mf.getCf();
console.log(cf.colo); // "DFW"
console.log(cf.country); // "US"
cf
Promise<IncomingRequestCfProperties>
Simulated cf object with geolocation and other properties

getServiceURL()

Get the URL for accessing a worker.
const url = await mf.getServiceURL("my-worker");
console.log(url); // "http://127.0.0.1:8787"
name
string
Worker name
url
Promise<URL>
URL for accessing the worker

setOptions()

Update Miniflare options without restarting.
await mf.setOptions({
  kvNamespaces: {
    MY_KV: "new-kv-id",
  },
});
options
Partial<MiniflareOptions>
required
New options to apply
void
Promise<void>
Resolves when options are applied

dispose()

Stop Miniflare and clean up resources.
await mf.dispose();
void
Promise<void>
Resolves when cleanup is complete

Binding Configuration

KV Namespaces

const mf = new Miniflare({
  script: '...',
  modules: true,
  kvNamespaces: {
    MY_KV: "kv-id",
  },
  kvPersist: true, // Persist to .mf/kv
});

const bindings = await mf.getBindings();
await bindings.MY_KV.put("key", "value");
kvNamespaces
Record<string, string>
Map of binding names to namespace IDs
kvPersist
boolean | string
Enable persistence (true for default path, or specify directory)

Durable Objects

const mf = new Miniflare({
  script: `
    export class Counter {
      constructor(state, env) {
        this.state = state;
      }
      async fetch(request) {
        let count = (await this.state.storage.get("count")) || 0;
        count++;
        await this.state.storage.put("count", count);
        return new Response(String(count));
      }
    }
    export default {
      async fetch(request, env) {
        const id = env.COUNTER.idFromName("global");
        const stub = env.COUNTER.get(id);
        return stub.fetch(request);
      }
    }
  `,
  modules: true,
  durableObjects: {
    COUNTER: "Counter",
  },
  durableObjectsPersist: true,
});
durableObjects
Record<string, string | DurableObjectOptions>
Map of binding names to class names or configuration objects
durableObjectsPersist
boolean | string
Enable persistence

R2 Buckets

const mf = new Miniflare({
  script: '...',
  modules: true,
  r2Buckets: {
    MY_BUCKET: "bucket-name",
  },
  r2Persist: true,
});

const bindings = await mf.getBindings();
await bindings.MY_BUCKET.put("file.txt", "Hello R2!");
const object = await bindings.MY_BUCKET.get("file.txt");
const text = await object.text();
r2Buckets
Record<string, string>
Map of binding names to bucket names
r2Persist
boolean | string
Enable persistence

D1 Databases

const mf = new Miniflare({
  script: '...',
  modules: true,
  d1Databases: {
    DB: "db-id",
  },
  d1Persist: true,
});

const bindings = await mf.getBindings();
await bindings.DB.exec(`
  CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);
  INSERT INTO users (name) VALUES ('Alice'), ('Bob');
`);
const { results } = await bindings.DB.prepare("SELECT * FROM users").all();
d1Databases
Record<string, string>
Map of binding names to database IDs
d1Persist
boolean | string
Enable persistence

Service Bindings

const mf = new Miniflare({
  workers: [
    {
      name: "api",
      script: `
        export default {
          async fetch(request, env) {
            const response = await env.AUTH.fetch("http://auth/verify");
            return new Response("Authenticated: " + response.ok);
          }
        }
      `,
      modules: true,
      serviceBindings: {
        AUTH: "auth",
      },
    },
    {
      name: "auth",
      script: `
        export default {
          async fetch() {
            return new Response("OK");
          }
        }
      `,
      modules: true,
    },
  ],
});

const response = await mf.dispatchFetch("http://localhost");
serviceBindings
Record<string, string | ServiceDesignator>
Map of binding names to service names or designators

Queue Bindings

const mf = new Miniflare({
  workers: [
    {
      name: "producer",
      script: `
        export default {
          async fetch(request, env) {
            await env.MY_QUEUE.send({ message: "Hello" });
            return new Response("Sent");
          }
        }
      `,
      modules: true,
      queueProducers: {
        MY_QUEUE: "my-queue",
      },
    },
    {
      name: "consumer",
      script: `
        export default {
          async queue(batch) {
            for (const message of batch.messages) {
              console.log(message.body);
            }
          }
        }
      `,
      modules: true,
      queueConsumers: {
        "my-queue": {
          maxBatchSize: 10,
          maxBatchTimeout: 5,
        },
      },
    },
  ],
});
queueProducers
Record<string, string>
Map of binding names to queue names
queueConsumers
Record<string, QueueConsumerOptions>
Map of queue names to consumer options

Environment Variables

const mf = new Miniflare({
  script: `
    export default {
      async fetch(request, env) {
        return new Response(env.API_KEY);
      }
    }
  `,
  modules: true,
  bindings: {
    API_KEY: "secret-key-123",
    DEBUG: true,
  },
});
bindings
Record<string, any>
Environment variables and simple bindings

Persistence

Miniflare can persist data to disk for all storage bindings.
const mf = new Miniflare({
  script: '...',
  modules: true,
  // Persist all storage to .mf directory
  kvPersist: true,
  d1Persist: true,
  r2Persist: true,
  durableObjectsPersist: true,
  cachePersist: true,
  // Or specify custom paths
  kvPersist: "./.data/kv",
  d1Persist: "./.data/d1",
});
defaultPersistRoot
string
Default root directory for all persisted data

Testing Example

import { Miniflare } from "miniflare";
import { beforeAll, afterAll, test, expect } from "vitest";

let mf: Miniflare;

beforeAll(async () => {
  mf = new Miniflare({
    scriptPath: "./src/index.ts",
    modules: true,
    kvNamespaces: { MY_KV: "test-kv" },
    d1Databases: { DB: "test-db" },
  });
});

afterAll(async () => {
  await mf.dispose();
});

test("GET /", async () => {
  const response = await mf.dispatchFetch("http://localhost/");
  expect(response.status).toBe(200);
  expect(await response.text()).toBe("Hello World");
});

test("KV storage", async () => {
  const bindings = await mf.getBindings();
  await bindings.MY_KV.put("test", "value");
  const value = await bindings.MY_KV.get("test");
  expect(value).toBe("value");
});

Advanced Features

Custom Logger

import { Log, LogLevel } from "miniflare";

class CustomLog extends Log {
  log(message: string) {
    console.log(`[LOG] ${message}`);
  }
  error(message: Error) {
    console.error(`[ERROR] ${message.stack}`);
  }
}

const mf = new Miniflare({
  script: '...',
  log: new CustomLog(),
});

Live Reload

import { watch } from "chokidar";

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

watch("./worker.js").on("change", async () => {
  await mf.setOptions({ scriptPath: "./worker.js" });
  console.log("Worker reloaded");
});

Build docs developers (and LLMs) love