# Cloudflare Workers SDK The Cloudflare Workers SDK is a comprehensive monorepo containing all the tools needed to build, test, and deploy serverless applications on Cloudflare's global network. The SDK provides a complete development experience from project creation to production deployment, with the core packages being Wrangler (CLI tool), Miniflare (local simulator), create-cloudflare (project scaffolding), and the Vite plugin for modern frontend integration. The SDK enables developers to write JavaScript/TypeScript code that runs at the edge on Cloudflare's network, with access to powerful primitives like KV storage, Durable Objects, R2 object storage, D1 databases, Queues, and AI services. The local development experience powered by Miniflare uses the same `workerd` runtime as production, ensuring consistent behavior between development and deployment. --- ## Wrangler CLI - Initialize Project Initialize a new Cloudflare Workers project with a basic configuration file and entry point. ```bash # Create a new Worker project interactively npx wrangler init my-worker # Create with default settings (no prompts) npx wrangler init my-worker -y # The generated wrangler.jsonc configuration file { "$schema": "node_modules/wrangler/config-schema.json", "name": "my-worker", "main": "./src/index.ts", "compatibility_date": "2024-01-01" } ``` --- ## Wrangler CLI - Local Development Server Start a local development server with hot reloading and devtools support for rapid iteration. ```bash # Start local development server npx wrangler dev # Start with specific entry point npx wrangler dev src/index.ts # Start on specific port with inspector npx wrangler dev --port 8080 --inspector-port 9229 # Start with remote bindings (connects to real Cloudflare services) npx wrangler dev --remote # Start with local persistence for storage bindings npx wrangler dev --persist-to .wrangler/state ``` --- ## Wrangler CLI - Deploy Worker Deploy a Worker to Cloudflare's global network with automatic bundling and optimization. ```bash # Deploy the Worker defined in wrangler.jsonc npx wrangler deploy # Deploy specific script with custom name npx wrangler deploy src/index.ts --name my-production-worker # Deploy with environment variables npx wrangler deploy --var API_KEY:secret123 --var DEBUG:true # Deploy to specific environment npx wrangler deploy --env production # Deploy with compatibility flags npx wrangler deploy --compatibility-date 2024-01-01 --compatibility-flag nodejs_compat ``` --- ## Wrangler CLI - KV Namespace Management Create and manage Workers KV namespaces for key-value storage at the edge. ```bash # Create a new KV namespace npx wrangler kv namespace create MY_KV # Output: Created namespace "my-worker-MY_KV" with id "abc123..." # List all KV namespaces npx wrangler kv namespace list # Put a value into KV npx wrangler kv key put --namespace-id abc123 "user:1" '{"name":"John"}' # Get a value from KV npx wrangler kv key get --namespace-id abc123 "user:1" # Bulk upload from JSON file npx wrangler kv bulk put --namespace-id abc123 data.json # Delete a key npx wrangler kv key delete --namespace-id abc123 "user:1" ``` --- ## Wrangler CLI - R2 Object Storage Manage R2 buckets for S3-compatible object storage with zero egress fees. ```bash # Create a new R2 bucket npx wrangler r2 bucket create my-bucket # List all R2 buckets npx wrangler r2 bucket list # Upload an object to R2 npx wrangler r2 object put my-bucket/images/photo.jpg --file ./photo.jpg # Download an object from R2 npx wrangler r2 object get my-bucket/images/photo.jpg --file ./downloaded.jpg # Delete an object npx wrangler r2 object delete my-bucket/images/photo.jpg ``` --- ## Wrangler CLI - D1 Database Create and manage D1 SQLite databases for serverless SQL at the edge. ```bash # Create a new D1 database npx wrangler d1 create my-database # Output: Created database "my-database" with id "abc123..." # List all D1 databases npx wrangler d1 list # Execute SQL query npx wrangler d1 execute my-database --command "SELECT * FROM users" # Execute SQL from file npx wrangler d1 execute my-database --file ./schema.sql # Create a migration npx wrangler d1 migrations create my-database create_users_table # Apply migrations npx wrangler d1 migrations apply my-database # Export database npx wrangler d1 export my-database --output backup.sql ``` --- ## Wrangler Configuration File Configure your Worker with bindings, routes, and build settings in wrangler.jsonc. ```jsonc { "$schema": "node_modules/wrangler/config-schema.json", "name": "my-api-worker", "main": "./src/index.ts", "compatibility_date": "2024-01-01", "compatibility_flags": ["nodejs_compat"], // KV Namespaces "kv_namespaces": [ { "binding": "MY_KV", "id": "abc123..." } ], // R2 Buckets "r2_buckets": [ { "binding": "MY_BUCKET", "bucket_name": "my-bucket" } ], // D1 Databases "d1_databases": [ { "binding": "DB", "database_name": "my-database", "database_id": "xyz789..." } ], // Durable Objects "durable_objects": { "bindings": [ { "name": "COUNTER", "class_name": "Counter" } ] }, // Environment Variables "vars": { "API_URL": "https://api.example.com", "DEBUG": "false" }, // Cron Triggers "triggers": { "crons": ["0 * * * *", "0 0 * * *"] }, // Static Assets "assets": { "directory": "./public" }, // Routes "routes": [ { "pattern": "api.example.com/*", "zone_name": "example.com" } ] } ``` --- ## Worker Entry Point - Fetch Handler Create a basic Worker that handles HTTP requests with the modern ES modules format. ```typescript // src/index.ts export interface Env { MY_KV: KVNamespace; DB: D1Database; MY_BUCKET: R2Bucket; API_KEY: string; } export default { async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { const url = new URL(request.url); // Route handling if (url.pathname === "/api/users") { const users = await env.DB.prepare("SELECT * FROM users").all(); return Response.json(users.results); } if (url.pathname.startsWith("/files/")) { const key = url.pathname.slice(7); const object = await env.MY_BUCKET.get(key); if (!object) return new Response("Not Found", { status: 404 }); return new Response(object.body, { headers: { "Content-Type": object.httpMetadata?.contentType || "application/octet-stream" } }); } // Cache API usage const cache = caches.default; const cached = await cache.match(request); if (cached) return cached; const response = new Response("Hello from Cloudflare Workers!"); ctx.waitUntil(cache.put(request, response.clone())); return response; }, // Scheduled handler for cron triggers async scheduled(controller: ScheduledController, env: Env, ctx: ExecutionContext) { console.log(`Cron triggered at ${controller.scheduledTime}`); // Perform scheduled tasks }, }; ``` --- ## Durable Objects Create stateful serverless objects with strong consistency guarantees. ```typescript // src/counter.ts export class Counter implements DurableObject { private state: DurableObjectState; private value: number = 0; constructor(state: DurableObjectState, env: Env) { this.state = state; // Load persisted value on initialization this.state.blockConcurrencyWhile(async () => { this.value = (await this.state.storage.get("value")) || 0; }); } async fetch(request: Request): Promise { const url = new URL(request.url); if (url.pathname === "/increment") { this.value++; await this.state.storage.put("value", this.value); return Response.json({ value: this.value }); } if (url.pathname === "/decrement") { this.value--; await this.state.storage.put("value", this.value); return Response.json({ value: this.value }); } return Response.json({ value: this.value }); } // Alarm handler for scheduled wake-ups async alarm(): Promise { console.log("Alarm triggered!"); // Perform cleanup or scheduled tasks } } // src/index.ts - Using Durable Objects export default { async fetch(request: Request, env: Env): Promise { const id = env.COUNTER.idFromName("global-counter"); const stub = env.COUNTER.get(id); return stub.fetch(request); }, }; ``` --- ## Miniflare - Local Development Simulator Use Miniflare programmatically to simulate Workers locally for testing and development. ```typescript import { Miniflare } from "miniflare"; // Create a Miniflare instance with inline script const mf = new Miniflare({ script: ` export default { async fetch(request, env) { const value = await env.MY_KV.get("key"); return new Response("Value: " + value); } } `, modules: true, kvNamespaces: ["MY_KV"], kvPersist: "./data/kv", }); // Send a request to the worker const response = await mf.dispatchFetch("http://localhost:8787/"); console.log(await response.text()); // Access bindings directly for testing const kv = await mf.getKVNamespace("MY_KV"); await kv.put("key", "test-value"); const value = await kv.get("key"); console.log(value); // "test-value" // Access D1 database const db = await mf.getD1Database("DB"); await db.exec("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)"); await db.prepare("INSERT INTO users (name) VALUES (?)").bind("Alice").run(); // Access R2 bucket const bucket = await mf.getR2Bucket("MY_BUCKET"); await bucket.put("file.txt", "Hello, R2!"); const object = await bucket.get("file.txt"); console.log(await object?.text()); // Cleanup await mf.dispose(); ``` --- ## Miniflare - Multi-Worker Configuration Configure multiple Workers with service bindings for microservices architecture. ```typescript import { Miniflare } from "miniflare"; const mf = new Miniflare({ workers: [ { name: "gateway", modules: true, script: ` export default { async fetch(request, env) { // Call the auth service const authResponse = await env.AUTH_SERVICE.fetch(request); if (!authResponse.ok) return authResponse; // Forward to API service return env.API_SERVICE.fetch(request); } } `, serviceBindings: { AUTH_SERVICE: "auth", API_SERVICE: "api", }, }, { name: "auth", modules: true, script: ` export default { async fetch(request, env) { const token = request.headers.get("Authorization"); if (token !== "Bearer secret") { return new Response("Unauthorized", { status: 401 }); } return new Response("OK"); } } `, }, { name: "api", modules: true, script: ` export default { async fetch(request, env) { return Response.json({ message: "Hello from API!" }); } } `, kvNamespaces: ["DATA"], }, ], kvPersist: true, }); // The first worker (gateway) receives all requests const response = await mf.dispatchFetch("http://localhost/api/data", { headers: { Authorization: "Bearer secret" }, }); console.log(await response.json()); // { message: "Hello from API!" } await mf.dispose(); ``` --- ## Miniflare - Durable Objects Testing Test Durable Objects locally with direct access to object instances. ```typescript import { Miniflare } from "miniflare"; const mf = new Miniflare({ modules: true, script: ` export class ChatRoom { constructor(state, env) { this.state = state; this.sessions = []; } async fetch(request) { const url = new URL(request.url); if (url.pathname === "/join") { const name = url.searchParams.get("name"); const messages = await this.state.storage.get("messages") || []; messages.push({ user: name, action: "joined" }); await this.state.storage.put("messages", messages); return Response.json({ success: true, messageCount: messages.length }); } if (url.pathname === "/messages") { const messages = await this.state.storage.get("messages") || []; return Response.json(messages); } return new Response("Not Found", { status: 404 }); } } export default { async fetch(request, env) { const url = new URL(request.url); const roomId = url.searchParams.get("room") || "default"; const id = env.CHAT_ROOM.idFromName(roomId); const room = env.CHAT_ROOM.get(id); return room.fetch(request); } } `, durableObjects: { CHAT_ROOM: "ChatRoom", }, durableObjectsPersist: "./data/do", }); // Test the Durable Object through the worker const joinResponse = await mf.dispatchFetch( "http://localhost/join?room=test&name=Alice" ); console.log(await joinResponse.json()); // { success: true, messageCount: 1 } // Get Durable Object namespace for direct testing const ns = await mf.getDurableObjectNamespace("CHAT_ROOM"); const id = ns.idFromName("test"); const stub = ns.get(id); const messages = await stub.fetch("http://localhost/messages"); console.log(await messages.json()); // [{ user: "Alice", action: "joined" }] await mf.dispose(); ``` --- ## getPlatformProxy - Framework Integration Use getPlatformProxy to access Cloudflare bindings in Node.js frameworks like Next.js, Remix, or Astro. ```typescript // For Next.js, Remix, Astro, or any Node.js framework import { getPlatformProxy } from "wrangler"; // Get platform proxy with bindings from wrangler.jsonc const { env, cf, ctx, caches, dispose } = await getPlatformProxy<{ MY_KV: KVNamespace; DB: D1Database; MY_BUCKET: R2Bucket; }>(); // Use KV binding await env.MY_KV.put("session:123", JSON.stringify({ userId: 1 })); const session = await env.MY_KV.get("session:123", "json"); // Use D1 binding const users = await env.DB.prepare("SELECT * FROM users WHERE active = ?") .bind(true) .all(); // Use R2 binding await env.MY_BUCKET.put("uploads/file.pdf", fileBuffer); // Access cf properties (request metadata) console.log(cf.country, cf.city, cf.timezone); // Use caches const cache = caches.default; await cache.put("https://example.com/data", new Response("cached")); // Clean up when done (important for tests) await dispose(); // With custom options const proxy = await getPlatformProxy({ configPath: "./wrangler.jsonc", environment: "production", persist: { path: "./.wrangler/state" }, }); ``` --- ## Wrangler Programmatic API - startWorker Start a Worker programmatically for custom tooling and advanced development workflows. ```typescript import { startWorker } from "wrangler"; const worker = await startWorker({ name: "my-worker", entrypoint: "./src/index.ts", compatibilityDate: "2024-01-01", compatibilityFlags: ["nodejs_compat"], bindings: { MY_VAR: { type: "plain_text", value: "hello" }, MY_SECRET: { type: "secret_text", value: "secret123" }, MY_KV: { type: "kv_namespace", id: "abc123" }, }, dev: { server: { hostname: "localhost", port: 8787 }, persist: "./.wrangler/state", liveReload: true, }, build: { bundle: true, minify: false, define: { "process.env.NODE_ENV": '"development"', }, }, }); // Wait for worker to be ready await worker.ready; console.log(`Worker running at ${await worker.url}`); // Send requests to the worker const response = await worker.fetch("http://localhost:8787/api/test"); console.log(await response.json()); // Trigger scheduled event await worker.scheduled({ cron: "* * * * *" }); // Update configuration without restart await worker.patchConfig({ bindings: { MY_VAR: { type: "plain_text", value: "updated" }, }, }); // Clean up await worker.dispose(); ``` --- ## create-cloudflare (C3) - Project Scaffolding Quickly scaffold new Cloudflare projects with interactive prompts or predefined templates. ```bash # Interactive project creation npm create cloudflare@latest # Create with specific template npm create cloudflare@latest my-app -- --template worker-typescript # Create a Pages project with React npm create cloudflare@latest my-react-app -- --framework react # Create with Next.js npm create cloudflare@latest my-nextjs-app -- --framework next # Create with Astro npm create cloudflare@latest my-astro-app -- --framework astro # Create with SvelteKit npm create cloudflare@latest my-svelte-app -- --framework svelte # Create a Hono API project npm create cloudflare@latest my-api -- --template hono # Create without deploying npm create cloudflare@latest my-app -- --no-deploy # Create in specific directory npm create cloudflare@latest ./projects/my-worker ``` --- ## Cloudflare Vite Plugin Integrate Vite with Workers for modern frontend development with full runtime API access. ```typescript // vite.config.ts import { defineConfig } from "vite"; import { cloudflare } from "@cloudflare/vite-plugin"; export default defineConfig({ plugins: [cloudflare()], }); // With React Router v7 or TanStack Start import { defineConfig } from "vite"; import { cloudflare } from "@cloudflare/vite-plugin"; import { reactRouter } from "@react-router/dev/vite"; export default defineConfig({ plugins: [ cloudflare(), reactRouter(), ], }); // Custom worker configuration import { defineConfig } from "vite"; import { cloudflare } from "@cloudflare/vite-plugin"; export default defineConfig({ plugins: [ cloudflare({ configPath: "./wrangler.jsonc", // Enable persistent storage during development persist: { path: "./.wrangler/state" }, }), ], }); ``` --- ## Vitest Pool Workers - Testing Write and run tests that execute inside the Workers runtime with full binding access. ```typescript // vitest.config.ts import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; import { defineConfig } from "vitest/config"; export default defineConfig({ plugins: [ cloudflareTest({ wrangler: { configPath: "./wrangler.jsonc" }, miniflare: { compatibilityFlags: ["service_binding_extra_handlers"], }, }), ], }); // tests/worker.test.ts import { exports, env } from "cloudflare:workers"; import { describe, it, expect, beforeAll } from "vitest"; describe("Worker", () => { beforeAll(async () => { // Setup test data await env.MY_KV.put("test-key", "test-value"); }); it("handles fetch requests", async () => { const response = await exports.default.fetch("http://localhost/api/data"); expect(response.status).toBe(200); const data = await response.json(); expect(data).toHaveProperty("message"); }); it("reads from KV", async () => { const value = await env.MY_KV.get("test-key"); expect(value).toBe("test-value"); }); it("writes to D1", async () => { await env.DB.exec("CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY, value TEXT)"); await env.DB.prepare("INSERT INTO test (value) VALUES (?)").bind("hello").run(); const result = await env.DB.prepare("SELECT * FROM test").all(); expect(result.results).toHaveLength(1); expect(result.results[0].value).toBe("hello"); }); it("triggers scheduled event", async () => { const result = await exports.default.scheduled({ scheduledTime: Date.now(), cron: "* * * * *", }); // Verify scheduled handler executed }); }); // tests/durable-object.test.ts import { env } from "cloudflare:workers"; import { describe, it, expect } from "vitest"; describe("Durable Object", () => { it("maintains state across requests", async () => { const id = env.COUNTER.idFromName("test"); const stub = env.COUNTER.get(id); // First request const res1 = await stub.fetch("http://localhost/increment"); expect(await res1.json()).toEqual({ value: 1 }); // Second request to same object const res2 = await stub.fetch("http://localhost/increment"); expect(await res2.json()).toEqual({ value: 2 }); }); }); ``` --- ## KV Asset Handler Serve static assets from Workers KV with caching and content-type handling. ```typescript import { getAssetFromKV, serveSinglePageApp, NotFoundError, MethodNotAllowedError, } from "@cloudflare/kv-asset-handler"; import manifestJSON from "__STATIC_CONTENT_MANIFEST"; const assetManifest = JSON.parse(manifestJSON); export default { async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { // API routes const url = new URL(request.url); if (url.pathname.startsWith("/api/")) { return handleApiRequest(request, env); } // Static assets from KV try { return await getAssetFromKV( { request, waitUntil: (p) => ctx.waitUntil(p) }, { ASSET_NAMESPACE: env.__STATIC_CONTENT, ASSET_MANIFEST: assetManifest, // Cache settings cacheControl: { browserTTL: 60 * 60 * 24, // 1 day edgeTTL: 60 * 60 * 24 * 7, // 1 week bypassCache: false, }, // For SPAs, serve index.html for all routes mapRequestToAsset: serveSinglePageApp, } ); } catch (e) { if (e instanceof NotFoundError) { return new Response("Page not found", { status: 404 }); } if (e instanceof MethodNotAllowedError) { return new Response("Method not allowed", { status: 405 }); } throw e; } }, }; // Custom asset mapping example async function handleWithCustomMapping(request: Request, env: Env, ctx: ExecutionContext) { const customKeyModifier = (req: Request) => { const url = new URL(req.url); // Strip /docs prefix and serve from different path const newPath = url.pathname.replace("/docs", "/documentation"); return new Request(new URL(newPath, url.origin), req); }; return getAssetFromKV( { request, waitUntil: (p) => ctx.waitUntil(p) }, { ASSET_NAMESPACE: env.__STATIC_CONTENT, ASSET_MANIFEST: assetManifest, mapRequestToAsset: customKeyModifier, } ); } ``` --- ## Queues - Message Processing Process messages asynchronously with Cloudflare Queues for background job handling. ```typescript // wrangler.jsonc { "queues": { "producers": [ { "binding": "MY_QUEUE", "queue": "my-queue" } ], "consumers": [ { "queue": "my-queue", "max_batch_size": 10, "max_retries": 3 } ] } } // src/index.ts export interface Env { MY_QUEUE: Queue; } interface QueueMessage { type: "email" | "notification" | "report"; payload: Record; } export default { // HTTP handler - enqueue messages async fetch(request: Request, env: Env): Promise { const url = new URL(request.url); if (url.pathname === "/send-email" && request.method === "POST") { const body = await request.json(); // Send single message await env.MY_QUEUE.send({ type: "email", payload: { to: body.email, subject: body.subject }, }); return Response.json({ queued: true }); } if (url.pathname === "/bulk-notify" && request.method === "POST") { const { userIds } = await request.json(); // Send batch of messages await env.MY_QUEUE.sendBatch( userIds.map((id: string) => ({ body: { type: "notification" as const, payload: { userId: id } }, })) ); return Response.json({ queued: userIds.length }); } return new Response("Not Found", { status: 404 }); }, // Queue consumer - process messages async queue(batch: MessageBatch, env: Env): Promise { for (const message of batch.messages) { try { switch (message.body.type) { case "email": await sendEmail(message.body.payload); message.ack(); break; case "notification": await sendNotification(message.body.payload); message.ack(); break; default: // Unknown message type, retry later message.retry(); } } catch (error) { console.error("Failed to process message:", error); message.retry({ delaySeconds: 60 }); } } }, }; ``` --- ## Hyperdrive - Database Connection Pooling Connect to external PostgreSQL databases with automatic connection pooling. ```typescript // wrangler.jsonc { "hyperdrive": [ { "binding": "HYPERDRIVE", "id": "abc123...", "localConnectionString": "postgres://user:pass@localhost:5432/mydb" } ] } // src/index.ts import { Client } from "pg"; export interface Env { HYPERDRIVE: Hyperdrive; } export default { async fetch(request: Request, env: Env): Promise { // Get connection string from Hyperdrive (pooled connection) const client = new Client({ connectionString: env.HYPERDRIVE.connectionString }); try { await client.connect(); const url = new URL(request.url); if (url.pathname === "/users") { const result = await client.query("SELECT id, name, email FROM users LIMIT 100"); return Response.json(result.rows); } if (url.pathname === "/user" && request.method === "POST") { const { name, email } = await request.json(); const result = await client.query( "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id", [name, email] ); return Response.json({ id: result.rows[0].id }); } return new Response("Not Found", { status: 404 }); } finally { await client.end(); } }, }; ``` --- ## AI Bindings - Workers AI Use Cloudflare's AI models for inference directly in your Worker. ```typescript // wrangler.jsonc { "ai": { "binding": "AI" } } // src/index.ts export interface Env { AI: Ai; } export default { async fetch(request: Request, env: Env): Promise { const url = new URL(request.url); // Text generation if (url.pathname === "/generate") { const { prompt } = await request.json(); const response = await env.AI.run("@cf/meta/llama-2-7b-chat-int8", { prompt, max_tokens: 256, }); return Response.json(response); } // Text classification if (url.pathname === "/classify") { const { text } = await request.json(); const response = await env.AI.run("@cf/huggingface/distilbert-sst-2-int8", { text, }); return Response.json(response); } // Image classification if (url.pathname === "/classify-image") { const imageData = await request.arrayBuffer(); const response = await env.AI.run("@cf/microsoft/resnet-50", { image: [...new Uint8Array(imageData)], }); return Response.json(response); } // Text embeddings if (url.pathname === "/embed") { const { text } = await request.json(); const response = await env.AI.run("@cf/baai/bge-base-en-v1.5", { text: [text], }); return Response.json(response); } // Translation if (url.pathname === "/translate") { const { text, source_lang, target_lang } = await request.json(); const response = await env.AI.run("@cf/meta/m2m100-1.2b", { text, source_lang, target_lang, }); return Response.json(response); } return new Response("Not Found", { status: 404 }); }, }; ``` --- ## Summary The Cloudflare Workers SDK provides a complete ecosystem for building serverless applications at the edge. The primary use cases include building API backends with Workers, serving static sites and SPAs with Workers Sites or Assets, processing background jobs with Queues, maintaining stateful applications with Durable Objects, and integrating AI capabilities directly into applications. The SDK excels in scenarios requiring low-latency global distribution, real-time data processing, and seamless integration with Cloudflare's broader platform services. Integration patterns typically involve using Wrangler CLI for development and deployment workflows, Miniflare for local testing and CI/CD pipelines, the Vite plugin for modern frontend frameworks like React Router and TanStack Start, and the Vitest pool for comprehensive testing. For existing Node.js frameworks like Next.js, Remix, or Astro, the `getPlatformProxy` API provides seamless access to Cloudflare bindings during local development. The monorepo structure ensures all tools work together cohesively, with shared types and utilities enabling type-safe development across the entire stack.