
Cloudflare
- 55 installs
- 14 repo stars
- Updated March 2, 2026
- oakoss/agent-skills
Helps with ai & agent building tasks during AI-assisted development.
About
cloudflare is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- cloudflare
- AI & Agent Building
- AI-coding skill
Cloudflare by the numbers
- 55 all-time installs (skills.sh)
- +1 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #6,762 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/oakoss/agent-skills --skill cloudflareAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 55 |
|---|---|
| repo stars | ★ 14 |
| Last updated | March 2, 2026 |
| Repository | oakoss/agent-skills ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Cloudflare
Overview
Cloudflare Workers is a serverless edge compute platform that runs JavaScript, TypeScript, and WebAssembly on Cloudflare's global network. Workers use the ES modules format with fetch, scheduled, and queue event handlers, and access platform services (KV, D1, R2, Durable Objects) through environment bindings.
When to use: Edge-first APIs, low-latency global apps, serverless functions, static site hosting with Pages, key-value caching, relational data at the edge, object storage without egress fees.
When NOT to use: Long-running compute exceeding CPU time limits, workloads requiring persistent TCP connections, applications needing full Node.js API compatibility, large monolithic applications better suited to containers.
Quick Reference
| Pattern | API / Config | Key Points |
|---|---|---|
| Fetch handler | export default { fetch(request, env, ctx) } | Entry point for HTTP requests |
| Scheduled handler | export default { scheduled(controller, env, ctx) } | Cron triggers via [triggers] in config |
| Environment variables | env.VAR_NAME | Set in wrangler.toml or dashboard |
| KV read/write | env.KV.get(key) / env.KV.put(key, value) | Eventually consistent, 25 MiB max value |
| D1 query | env.DB.prepare(sql).bind(...params).all() | SQLite at the edge, prepared statements |
| D1 batch | env.DB.batch([stmt1, stmt2]) | Atomic transaction, single round trip |
| R2 upload | env.BUCKET.put(key, body) | S3-compatible, no egress fees |
| R2 download | env.BUCKET.get(key) | Returns R2ObjectBody with ReadableStream |
| Durable Objects | env.DO.get(id) then stub.fetch(request) | Single-instance stateful coordination |
| DO storage | this.ctx.storage.sql.exec(query) | SQLite backend recommended for new DOs |
| Pages deploy | wrangler pages deploy <dir> | Static + Functions, auto-generated routes |
| Worker deploy | wrangler deploy | Reads wrangler.toml for config |
| Dev server | wrangler dev | Local development with bindings |
| Secrets | wrangler secret put SECRET_NAME | Encrypted, not in wrangler.toml |
| Static assets | assets: { directory: "./dist" } | Served without invoking Worker by default |
| Workflows | export class MyWorkflow extends WorkflowEntrypoint | Durable multi-step execution engine |
| Hyperdrive | env.HYPERDRIVE.connectionString | Connection pooling for external PostgreSQL |
| Queues produce | env.QUEUE.send(message) | Async message passing between Workers |
| Config format | wrangler.jsonc (recommended for new projects) | JSON with comments; preferred over TOML |
Common Mistakes
| Mistake | Correct Pattern |
|---|---|
| Accessing bindings as globals | Access via env parameter in module format |
| Storing secrets in wrangler.toml | Use wrangler secret put for encrypted values |
| Using KV for strong consistency | Use D1 or Durable Objects for consistent reads |
| Not binding parameters in D1 queries | Always use .prepare().bind() to prevent SQL injection |
| Exceeding KV value size (25 MiB) | Use R2 for objects larger than 25 MiB |
Forgetting await on storage operations | KV, D1, R2 methods are all async |
Using fetch() without passing signal | Pass request.signal for automatic cancellation |
| Creating D1 database in Worker code | Create with wrangler d1 create, bind in config |
Missing compatibility_date in config | Always set to avoid breaking changes |
| Returning non-Response from fetch handler | Must return a Response object |
Using wrangler.toml for new projects | Use wrangler.jsonc for new projects; has better IDE support |
| Using KV for relational queries | Use D1 for SQL queries; KV is key-value only |
Delegation
- Architecture review: Use
Taskagent to evaluate edge vs origin patterns - Code review: Delegate to
code-revieweragent - Storage selection: Use
Exploreagent to compare KV vs D1 vs R2 vs DO
References
- Workers syntax, fetch handler, scheduled events, and bindings
- KV, D1, R2, and Durable Objects storage APIs
- Cloudflare Pages, functions, routes, and build configuration
- Wrangler CLI, configuration, dev server, deploy, and secrets
Pages
Overview
Cloudflare Pages is a full-stack hosting platform for static sites and dynamic applications. It supports automatic builds from Git, Functions for server-side logic, and integrates with all Cloudflare bindings (KV, D1, R2, Durable Objects).
Project Structure
my-pages-project/
├── public/ # Static assets
│ ├── index.html
│ └── assets/
├── functions/ # Server-side functions (file-based routing)
│ ├── api/
│ │ ├── users.ts # /api/users
│ │ └── [id].ts # /api/:id (dynamic route)
│ └── _middleware.ts # Runs before all functions
├── _redirects # Redirect rules
├── _headers # Custom response headers
└── wrangler.toml # Configuration (optional)Pages Functions
Functions use file-based routing in the functions/ directory. Each file exports HTTP method handlers.
Basic Function
interface Env {
DB: D1Database;
MY_KV: KVNamespace;
}
export const onRequestGet: PagesFunction<Env> = async (context) => {
const { request, env, params, waitUntil } = context;
const users = await env.DB.prepare('SELECT * FROM users').all();
return Response.json(users.results);
};
export const onRequestPost: PagesFunction<Env> = async (context) => {
const body = await context.request.json();
await context.env.DB.prepare('INSERT INTO users (name) VALUES (?)')
.bind(body.name)
.run();
return new Response('Created', { status: 201 });
};Dynamic Routes
File names with brackets create dynamic route parameters:
export const onRequestGet: PagesFunction<Env> = async (context) => {
const userId = context.params.id;
const user = await context.env.DB.prepare('SELECT * FROM users WHERE id = ?')
.bind(userId)
.first();
if (!user) {
return new Response('Not Found', { status: 404 });
}
return Response.json(user);
};Catch-All Routes
Use [[path]].ts for catch-all segments:
export const onRequestGet: PagesFunction = async (context) => {
const segments = context.params.path;
return new Response(`Caught: ${segments}`);
};Middleware
_middleware.ts files run before functions at the same or deeper directory level:
const authMiddleware: PagesFunction<Env> = async (context) => {
const token = context.request.headers.get('Authorization');
if (!token) {
return new Response('Unauthorized', { status: 401 });
}
const user = await validateToken(token, context.env);
context.data.user = user;
return context.next();
};
const loggingMiddleware: PagesFunction = async (context) => {
const start = Date.now();
const response = await context.next();
const duration = Date.now() - start;
console.log(
`${context.request.method} ${context.request.url} - ${duration}ms`,
);
return response;
};
export const onRequest = [loggingMiddleware, authMiddleware];Routes Configuration
\_routes.json
Controls which requests invoke Functions vs serve static assets. Placed in the build output directory. Auto-generated when a functions/ directory is detected.
{
"version": 1,
"include": ["/api/*"],
"exclude": ["/assets/*", "/images/*"]
}includepatterns invoke the Functionexcludepatterns serve static assets directly (unlimited free requests)- Exclude patterns take priority over include patterns
\_redirects
Place in the project root or build output. One rule per line.
/old-page /new-page 301
/blog/* /posts/:splat 302
/home / 301
/docs https://docs.example.com 302- Status codes:
301(permanent),302(temporary),200(rewrite, serves content from target without changing URL) :splatcaptures wildcard matches- Max 2,000 redirect rules
\_headers
Custom response headers for static assets:
/api/*
Access-Control-Allow-Origin: *
/*.js
Cache-Control: public, max-age=31536000, immutable
/secure/*
X-Frame-Options: DENY
Content-Security-Policy: default-src 'self'Build Configuration
wrangler.toml for Pages
name = "my-pages-project"
pages_build_output_dir = "dist"
compatibility_date = "2024-09-23"
[vars]
API_URL = "https://api.example.com"
[[kv_namespaces]]
binding = "MY_KV"
id = "abc123"
[[d1_databases]]
binding = "DB"
database_name = "my-db"
database_id = "def456"
[[r2_buckets]]
binding = "BUCKET"
bucket_name = "my-bucket"Environment-Specific Config
[env.preview.vars]
API_URL = "https://staging-api.example.com"
[env.production.vars]
API_URL = "https://api.example.com"Deployment
Git Integration
Connect a repository for automatic builds. Each push triggers a deployment:
main/productionbranch deploys to production URL- Other branches create preview deployments with unique URLs
Wrangler CLI Deployment
npm run build
wrangler pages deploy dist
wrangler pages deploy dist --project-name=my-project
wrangler pages deploy dist --branch=stagingCompile Functions Separately
When migrating or needing a single Worker script from Pages Functions:
wrangler pages functions build --outdir=dist/_worker.jsFramework Integration
Pages auto-detects popular frameworks and sets build commands:
| Framework | Build Command | Output Directory |
|---|---|---|
| Next.js | npx @cloudflare/next-on-pages | .vercel/output/static |
| Astro | npm run build | dist |
| Remix | npm run build | build/client |
| SvelteKit | npm run build | .svelte-kit/cloudflare |
| Nuxt | npm run build | dist |
| Vite/React | npm run build | dist |
SPA Fallback
For single-page applications, Pages automatically serves index.html for any path that does not match a static file. No additional configuration required.
Storage
KV (Key-Value Store)
Eventually consistent, global key-value storage optimized for high-read, low-write workloads. Max value size is 25 MiB. Max key size is 512 bytes.
Basic Operations
export default {
async fetch(request: Request, env: Env): Promise<Response> {
await env.MY_KV.put('user:123', JSON.stringify({ name: 'Alice' }));
const value = await env.MY_KV.get('user:123');
const json = await env.MY_KV.get<{ name: string }>('user:123', {
type: 'json',
});
await env.MY_KV.delete('user:123');
return Response.json({ value, json });
},
};KV with Metadata and Expiration
await env.MY_KV.put('session:abc', JSON.stringify(sessionData), {
expirationTtl: 3600,
metadata: { userId: '123', role: 'admin' },
});
const { value, metadata } = await env.MY_KV.getWithMetadata<
string,
{ userId: string; role: string }
>('session:abc');Listing Keys
async function listAllKeys(
kv: KVNamespace,
prefix?: string,
): Promise<string[]> {
const keys: string[] = [];
let cursor: string | undefined;
do {
const result = await kv.list({ prefix, cursor });
keys.push(...result.keys.map((k) => k.name));
cursor = result.list_complete ? undefined : result.cursor;
} while (cursor);
return keys;
}Wrangler Configuration
[[kv_namespaces]]
binding = "MY_KV"
id = "abc123def456"
preview_id = "preview_abc123"D1 (SQLite Database)
Server-side SQLite database with prepared statements. Strongly consistent within a region. Use .prepare().bind() for all queries to prevent SQL injection.
Basic Queries
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const allUsers = await env.DB.prepare('SELECT * FROM users').all();
const user = await env.DB.prepare('SELECT * FROM users WHERE id = ?')
.bind(1)
.first();
await env.DB.prepare('INSERT INTO users (name, email) VALUES (?, ?)')
.bind('Alice', 'alice@example.com')
.run();
return Response.json({ users: allUsers.results, user });
},
};Batch Queries (Atomic Transactions)
All statements execute in a single transaction. If any statement fails, the entire batch is rolled back.
const results = await env.DB.batch([
env.DB.prepare('INSERT INTO orders (user_id, total) VALUES (?, ?)').bind(
1,
99.99,
),
env.DB.prepare(
'UPDATE users SET order_count = order_count + 1 WHERE id = ?',
).bind(1),
env.DB.prepare('INSERT INTO audit_log (action, user_id) VALUES (?, ?)').bind(
'order_created',
1,
),
]);Query Result Shape
const result = await env.DB.prepare('SELECT * FROM users').all();The result object contains:
interface D1Result<T> {
results: T[];
success: boolean;
meta: {
duration: number;
rows_read: number;
rows_written: number;
last_row_id: number;
changed_db: boolean;
changes: number;
size_after: number;
};
}Schema Migrations
Create and apply migrations with the Wrangler CLI:
wrangler d1 migrations create my-db create-users-table
wrangler d1 migrations apply my-db
wrangler d1 migrations apply my-db --remoteWrangler Configuration
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "abc123-def456-ghi789"R2 (Object Storage)
S3-compatible object storage with zero egress fees. Max object size is 5 TiB (multipart). Single PUT max is 5 GiB.
Basic Operations
export default {
async fetch(request: Request, env: Env): Promise<Response> {
await env.BUCKET.put('images/photo.png', request.body, {
httpMetadata: { contentType: 'image/png' },
customMetadata: { uploadedBy: 'user-123' },
});
const object = await env.BUCKET.get('images/photo.png');
if (!object) {
return new Response('Not Found', { status: 404 });
}
const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set('ETag', object.httpEtag);
return new Response(object.body, { headers });
},
};Listing Objects
const listed = await env.BUCKET.list({
prefix: 'images/',
limit: 100,
cursor: undefined,
});
for (const object of listed.objects) {
console.log(`${object.key} - ${object.size} bytes`);
}
if (listed.truncated) {
const nextPage = await env.BUCKET.list({
prefix: 'images/',
cursor: listed.cursor,
});
}Deleting Objects
await env.BUCKET.delete('images/photo.png');
await env.BUCKET.delete(['images/a.png', 'images/b.png', 'images/c.png']);Conditional Operations
const object = await env.BUCKET.get('data.json', {
onlyIf: {
etagMatches: request.headers.get('If-None-Match') ?? undefined,
},
});
if (object && !('body' in object)) {
return new Response(null, { status: 304 });
}Multipart Upload
const multipart = await env.BUCKET.createMultipartUpload('large-file.zip', {
httpMetadata: { contentType: 'application/zip' },
});
const part1 = await multipart.uploadPart(1, chunk1);
const part2 = await multipart.uploadPart(2, chunk2);
const finalObject = await multipart.complete([part1, part2]);Wrangler Configuration
[[r2_buckets]]
binding = "BUCKET"
bucket_name = "my-bucket"
preview_bucket_name = "my-bucket-preview"Durable Objects
Single-instance stateful objects for coordination, counters, WebSockets, and rate limiting. Each instance runs in one location and provides strong consistency.
SQLite Storage (Recommended)
import { DurableObject } from 'cloudflare:workers';
export class ChatRoom extends DurableObject<Env> {
sql = this.ctx.storage.sql;
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
this.sql.exec(`
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
author TEXT NOT NULL,
content TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now'))
)
`);
}
async addMessage(author: string, content: string): Promise<void> {
this.sql.exec(
'INSERT INTO messages (author, content) VALUES (?, ?)',
author,
content,
);
}
async getMessages(limit = 50): Promise<unknown[]> {
return this.sql
.exec('SELECT * FROM messages ORDER BY id DESC LIMIT ?', limit)
.toArray();
}
}KV Storage (Legacy)
import { DurableObject } from 'cloudflare:workers';
export class Counter extends DurableObject<Env> {
async increment(): Promise<number> {
const value = (await this.ctx.storage.get<number>('count')) ?? 0;
const newValue = value + 1;
await this.ctx.storage.put('count', newValue);
return newValue;
}
async getCount(): Promise<number> {
return (await this.ctx.storage.get<number>('count')) ?? 0;
}
}WebSocket Hibernation
import { DurableObject } from 'cloudflare:workers';
export class WebSocketRoom extends DurableObject<Env> {
async fetch(request: Request): Promise<Response> {
const pair = new WebSocketPair();
const [client, server] = Object.values(pair);
this.ctx.acceptWebSocket(server);
return new Response(null, { status: 101, webSocket: client });
}
webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): void {
this.ctx.getWebSockets().forEach((socket) => {
if (socket !== ws) {
socket.send(
typeof message === 'string' ? message : new Uint8Array(message),
);
}
});
}
webSocketClose(ws: WebSocket): void {
ws.close();
}
}Wrangler Configuration
[durable_objects]
bindings = [
{ name = "CHAT_ROOM", class_name = "ChatRoom" },
{ name = "COUNTER", class_name = "Counter" },
]
[[migrations]]
tag = "v1"
new_sqlite_classes = ["ChatRoom"]
new_classes = ["Counter"]Alarms
Schedule a single timer per Durable Object instance:
import { DurableObject } from 'cloudflare:workers';
export class Scheduler extends DurableObject<Env> {
async scheduleTask(delayMs: number): Promise<void> {
await this.ctx.storage.setAlarm(Date.now() + delayMs);
}
async alarm(): Promise<void> {
await this.performScheduledWork();
}
}Workers
Module Format
Workers use ES module syntax with a default export containing event handlers. The env parameter provides access to all bindings (KV, D1, R2, Durable Objects, secrets, variables).
export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext,
): Promise<Response> {
return new Response('Hello from the edge');
},
};Environment Type
Define an Env interface for type-safe binding access:
interface Env {
MY_KV: KVNamespace;
DB: D1Database;
BUCKET: R2Bucket;
MY_DO: DurableObjectNamespace;
API_KEY: string;
ENVIRONMENT: string;
}Fetch Handler
The primary handler for HTTP requests. Receives the incoming Request, environment bindings, and an execution context.
export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext,
): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === '/api/data') {
const data = await env.MY_KV.get('key', { type: 'json' });
return Response.json(data);
}
if (request.method === 'POST' && url.pathname === '/api/submit') {
const body = await request.json();
await env.DB.prepare('INSERT INTO submissions (data) VALUES (?)')
.bind(JSON.stringify(body))
.run();
return new Response('Created', { status: 201 });
}
return new Response('Not Found', { status: 404 });
},
};Execution Context
The ctx parameter provides lifecycle methods:
export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext,
): Promise<Response> {
ctx.waitUntil(logAnalytics(request, env));
ctx.passThroughOnException();
return new Response('OK');
},
};ctx.waitUntil(promise)keeps the Worker alive after returning a response to perform background work (logging, cache updates)ctx.passThroughOnException()falls through to the origin server if the Worker throws
Scheduled Handler
Triggered by cron expressions configured in wrangler.toml. Useful for periodic tasks like cleanup, aggregation, or external API polling.
export default {
async scheduled(
controller: ScheduledController,
env: Env,
ctx: ExecutionContext,
): Promise<void> {
ctx.waitUntil(performCleanup(env));
},
};[triggers]
crons = ["0 * * * *", "0 0 * * *"]The controller provides:
controller.scheduledTime— the time the cron was scheduled (milliseconds since epoch)controller.cron— the cron pattern that triggered the event
Queue Handler
Processes messages from Cloudflare Queues:
export default {
async queue(
batch: MessageBatch<unknown>,
env: Env,
ctx: ExecutionContext,
): Promise<void> {
for (const message of batch.messages) {
await processMessage(message.body, env);
message.ack();
}
},
};Email Handler
Processes incoming emails via Email Routing:
export default {
async email(
message: EmailMessage,
env: Env,
ctx: ExecutionContext,
): Promise<void> {
const { from, to } = message;
const content = await new Response(message.raw).text();
await env.DB.prepare(
'INSERT INTO emails (sender, recipient, body) VALUES (?, ?, ?)',
)
.bind(from, to, content)
.run();
},
};WorkerEntrypoint (RPC)
Extend WorkerEntrypoint to expose methods for Service Bindings with RPC:
import { WorkerEntrypoint } from 'cloudflare:workers';
export default class extends WorkerEntrypoint<Env> {
async fetch(request: Request): Promise<Response> {
return new Response('Hello');
}
async greet(name: string): Promise<string> {
return `${this.env.GREETING} ${name}`;
}
}The calling Worker invokes RPC methods directly on the binding:
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const greeting = await env.MY_SERVICE.greet('World');
return new Response(greeting);
},
};DurableObject Entrypoint
Extend DurableObject for stateful, single-instance coordination:
import { DurableObject } from 'cloudflare:workers';
export class Counter extends DurableObject<Env> {
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === '/increment') {
const value = (await this.ctx.storage.get<number>('count')) ?? 0;
await this.ctx.storage.put('count', value + 1);
return Response.json({ count: value + 1 });
}
const count = (await this.ctx.storage.get<number>('count')) ?? 0;
return Response.json({ count });
}
}Accessing a Durable Object from a Worker:
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const id = env.COUNTER.idFromName('global');
const stub = env.COUNTER.get(id);
return stub.fetch(request);
},
};Request and Response Patterns
Workers use the standard Web API Request and Response objects:
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
const headers = Object.fromEntries(request.headers);
const method = request.method;
const body = method === 'POST' ? await request.json() : null;
const cfProperties = request.cf;
return Response.json(
{ url: url.pathname, method, country: cfProperties?.country },
{
status: 200,
headers: {
'Content-Type': 'application/json',
'Cache-Control': 's-maxage=60',
},
},
);
},
};CORS Handling
function corsHeaders(origin: string): HeadersInit {
return {
'Access-Control-Allow-Origin': origin,
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
};
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const origin = request.headers.get('Origin') ?? '*';
if (request.method === 'OPTIONS') {
return new Response(null, { headers: corsHeaders(origin) });
}
const response = await handleRequest(request, env);
const newHeaders = new Headers(response.headers);
Object.entries(corsHeaders(origin)).forEach(([key, value]) => {
newHeaders.set(key, value);
});
return new Response(response.body, {
status: response.status,
headers: newHeaders,
});
},
};Error Handling
export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext,
): Promise<Response> {
try {
return await handleRequest(request, env);
} catch (error) {
const message =
error instanceof Error ? error.message : 'Internal Server Error';
return Response.json({ error: message }, { status: 500 });
}
},
};Environment Variables Configuration
Variables are set in wrangler.toml and accessed via env:
[vars]
API_HOST = "https://api.example.com"
ENVIRONMENT = "production"For environment-specific overrides:
[env.staging.vars]
API_HOST = "https://staging-api.example.com"
ENVIRONMENT = "staging"Wrangler
Installation
npm install -D wrangler
npx wrangler --version
npx wrangler loginCore Commands
| Command | Description |
|---|---|
wrangler init | Create a new Worker project |
wrangler dev | Start local dev server with bindings |
wrangler deploy | Deploy Worker to Cloudflare |
wrangler delete | Delete a deployed Worker |
wrangler tail | Stream live logs from production |
wrangler secret put NAME | Set an encrypted secret |
wrangler secret list | List all secrets |
wrangler secret delete NAME | Remove a secret |
wrangler pages deploy DIR | Deploy a Pages project |
wrangler d1 create DB_NAME | Create a D1 database |
wrangler d1 execute DB_NAME --command "SQL" | Run SQL on D1 |
wrangler d1 migrations apply DB_NAME | Apply D1 migrations |
wrangler r2 bucket create BUCKET_NAME | Create an R2 bucket |
wrangler kv namespace create NS_NAME | Create a KV namespace |
Configuration File
The primary configuration file for Workers and Pages projects. Supports both JSON (wrangler.json / wrangler.jsonc) and TOML (wrangler.toml) formats. `wrangler.jsonc` is recommended for new projects -- it provides JSON Schema validation, IDE autocompletion, and inline comments. Some newer Wrangler features are only available in the JSON format.
Minimal Worker Config
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-09-23"Full Worker Config
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-09-23"
compatibility_flags = ["nodejs_compat"]
[observability]
enabled = true
[vars]
ENVIRONMENT = "production"
API_URL = "https://api.example.com"
[[kv_namespaces]]
binding = "CACHE"
id = "abc123def456"
preview_id = "preview_abc123"
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "abc123-def456-ghi789"
[[r2_buckets]]
binding = "ASSETS"
bucket_name = "my-assets"
preview_bucket_name = "my-assets-preview"
[durable_objects]
bindings = [
{ name = "COUNTER", class_name = "Counter" },
{ name = "ROOM", class_name = "ChatRoom" },
]
[[migrations]]
tag = "v1"
new_sqlite_classes = ["ChatRoom"]
new_classes = ["Counter"]
[[queues.producers]]
queue = "my-queue"
binding = "QUEUE"
[[queues.consumers]]
queue = "my-queue"
max_batch_size = 10
max_batch_timeout = 5
[triggers]
crons = ["*/5 * * * *"]
[[rules]]
type = "Text"
globs = ["**/*.html"]
fallthrough = truePages Config
name = "my-pages-project"
pages_build_output_dir = "dist"
compatibility_date = "2024-09-23"
[vars]
API_URL = "https://api.example.com"
[[kv_namespaces]]
binding = "MY_KV"
id = "abc123"JSON Config Format
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2024-09-23",
"kv_namespaces": [
{
"binding": "CACHE",
"id": "abc123def456",
},
],
}Environments
Define per-environment overrides for staging, production, or custom environments:
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-09-23"
[vars]
ENVIRONMENT = "production"
[env.staging]
name = "my-worker-staging"
[env.staging.vars]
ENVIRONMENT = "staging"
[env.preview]
name = "my-worker-preview"
[env.preview.vars]
ENVIRONMENT = "preview"Deploy to a specific environment:
wrangler deploy --env staging
wrangler dev --env stagingLocal Development
Dev Server
wrangler dev
wrangler dev --port 8787
wrangler dev --local
wrangler dev --remoteThe dev server provides:
- Local KV, D1, R2, and Durable Object simulation via Miniflare
- Hot reloading on file changes
- Access to
request.cfproperties in remote mode - Local persistence in
.wrangler/state/
Persist Local State
wrangler dev --persist-to .wrangler/stateLocal D1, KV, and R2 data persists between dev sessions at this path.
Secrets Management
Secrets are encrypted environment variables that never appear in wrangler.toml or logs.
wrangler secret put API_KEY
echo "my-secret-value" | wrangler secret put API_KEY
wrangler secret list
wrangler secret delete API_KEY
wrangler secret put API_KEY --env stagingAccess secrets the same way as environment variables in Worker code:
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const apiKey = env.API_KEY;
return new Response('OK');
},
};Tail (Live Logs)
Stream real-time logs from a deployed Worker:
wrangler tail
wrangler tail --format json
wrangler tail --status error
wrangler tail --method POST
wrangler tail --search "user-123"
wrangler tail --env stagingD1 CLI Operations
wrangler d1 create my-database
wrangler d1 list
wrangler d1 execute my-database --command "SELECT * FROM users"
wrangler d1 execute my-database --file schema.sql
wrangler d1 execute my-database --command "SELECT 1" --remote
wrangler d1 migrations create my-database migration-name
wrangler d1 migrations apply my-database
wrangler d1 migrations apply my-database --remoteKV CLI Operations
wrangler kv namespace create MY_KV
wrangler kv namespace list
wrangler kv key put --binding=MY_KV "key" "value"
wrangler kv key get --binding=MY_KV "key"
wrangler kv key list --binding=MY_KV
wrangler kv key list --binding=MY_KV --prefix="user:"
wrangler kv key delete --binding=MY_KV "key"
wrangler kv bulk put --binding=MY_KV data.jsonR2 CLI Operations
wrangler r2 bucket create my-bucket
wrangler r2 bucket list
wrangler r2 object put my-bucket/path/file.txt --file ./local-file.txt
wrangler r2 object get my-bucket/path/file.txt
wrangler r2 object delete my-bucket/path/file.txtCompatibility Dates and Flags
The compatibility_date controls which Workers runtime behavior your code uses. Set it to the current date when creating a project, and update it periodically to opt into new behavior.
compatibility_date = "2024-09-23"
compatibility_flags = ["nodejs_compat"]Common compatibility flags:
| Flag | Purpose |
|---|---|
nodejs_compat | Enable Node.js built-in module support |
streams_enable_constructors | Enable ReadableStream / WritableStream constructors |
transformstream_enable_standard_constructor | Standard TransformStream constructor |
Custom Domains and Routes
routes = [
{ pattern = "example.com/api/*", zone_name = "example.com" },
{ pattern = "api.example.com/*", zone_name = "example.com" },
]
[env.production]
routes = [
{ pattern = "example.com/api/*", zone_name = "example.com" },
]Or use custom domains (automatically provisions SSL):
[env.production]
workers_dev = false
routes = [
{ pattern = "api.example.com", custom_domain = true },
]