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
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.
Configuration options Show MiniflareOptions (single worker)
Worker name (defaults to empty string)
Worker script content (mutually exclusive with scriptPath)
Path to worker script file
Whether the worker uses ES modules format
Root directory for resolving module imports
Runtime compatibility date (e.g., “2024-01-01”)
Runtime compatibility flags
Route patterns this worker handles
Host to bind HTTP server to (default: “127.0.0.1”)
Port for HTTP server (0 for random port)
Show Multi-worker configuration
For running multiple workers in the same Miniflare instance: {
workers : [
{
name: "worker-1" ,
script: "export default { ... }" ,
modules: true ,
},
{
name: "worker-2" ,
scriptPath: "./worker2.js" ,
}
]
}
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" }),
});
Request initialization options
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" },
]);
Worker name (optional for single-worker setups)
Worker instance with methods: Show MiniflareWorker methods
fetch
(input: RequestInfo, init?: RequestInit) => Promise<Response>
Send HTTP request to this worker
scheduled
(options?: ScheduledOptions) => Promise<void>
Trigger a scheduled event await worker . scheduled ({ cron: "0 0 * * *" });
queue
(queueName: string, messages: Message[]) => Promise<void>
Send messages to a queue consumer await worker . queue ( "my-queue" , [
{ id: "1" , timestamp: Date . now (), body: { key: "value" } },
]);
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" );
Worker name (optional for single-worker setups)
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"
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
Resolves when options are applied
dispose()
Stop Miniflare and clean up resources.
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" );
Map of binding names to namespace IDs
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 Show DurableObjectOptions
Durable Object class name
Worker name that exports this class (for external DOs)
Custom unique key for ID generation
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 ();
Map of binding names to bucket names
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 ();
Map of binding names to database IDs
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 ,
},
},
},
],
});
Map of binding names to queue names
queueConsumers
Record<string, QueueConsumerOptions>
Map of queue names to consumer options Show QueueConsumerOptions
Maximum messages per batch (default: 10)
Maximum seconds to wait (default: 5)
Number of retries on failure
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 ,
},
});
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" ,
});
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" );
});