Configuration
This guide covers everything you need to configure agents for local development and production deployment, including Wrangler configuration file setup, type generation, environment variables, and the Cloudflare dashboard.
The typical file structure for an Agent project created from npm create cloudflare@latest agents-starter -- --template cloudflare/agents-starter follows:
Directorysrc/
- index.ts your Agent definition
Directorypublic/
- index.html
Directorytest/
- index.spec.ts your tests
- package.json
- tsconfig.json
- vitest.config.mts
- worker-configuration.d.ts
- wrangler.jsonc your Workers and Agent configuration
The wrangler.jsonc file configures your Cloudflare Worker and its bindings. Here is a complete example for an agents project:
{ "$schema": "node_modules/wrangler/config-schema.json", "name": "my-agent-app", "main": "src/server.ts", "compatibility_date": "2025-01-01", "compatibility_flags": ["nodejs_compat"],
// Static assets (optional) "assets": { "directory": "public", "binding": "ASSETS", },
// Durable Object bindings for agents "durable_objects": { "bindings": [ { "name": "MyAgent", "class_name": "MyAgent", }, { "name": "ChatAgent", "class_name": "ChatAgent", }, ], },
// Required: Enable SQLite storage for agents "migrations": [ { "tag": "v1", "new_sqlite_classes": ["MyAgent", "ChatAgent"], }, ],
// AI binding (optional, for Workers AI) "ai": { "binding": "AI", },
// Observability (recommended) "observability": { "enabled": true, },}"$schema" = "node_modules/wrangler/config-schema.json"name = "my-agent-app"main = "src/server.ts"compatibility_date = "2025-01-01"compatibility_flags = [ "nodejs_compat" ]
[assets]directory = "public"binding = "ASSETS"
[[durable_objects.bindings]]name = "MyAgent"class_name = "MyAgent"
[[durable_objects.bindings]]name = "ChatAgent"class_name = "ChatAgent"
[[migrations]]tag = "v1"new_sqlite_classes = [ "MyAgent", "ChatAgent" ]
[ai]binding = "AI"
[observability]enabled = trueThe nodejs_compat flag is required for agents:
{ "compatibility_flags": ["nodejs_compat"],}compatibility_flags = [ "nodejs_compat" ]This enables Node.js compatibility mode, which agents depend on for crypto, streams, and other Node.js APIs.
Each agent class needs a binding:
{ "durable_objects": { "bindings": [ { "name": "Counter", "class_name": "Counter", }, ], },}[[durable_objects.bindings]]name = "Counter"class_name = "Counter"| Field | Description |
|---|---|
name | The property name on env. Use this in code: env.Counter |
class_name | Must match the exported class name exactly |
Migrations tell Cloudflare how to set up storage for your Durable Objects:
{ "migrations": [ { "tag": "v1", "new_sqlite_classes": ["MyAgent"], }, ],}[[migrations]]tag = "v1"new_sqlite_classes = [ "MyAgent" ]| Field | Description |
|---|---|
tag | Version identifier (for example, "v1", "v2"). Must be unique |
new_sqlite_classes | Agent classes that use SQLite storage (state persistence) |
deleted_classes | Classes being removed |
renamed_classes | Classes being renamed |
For serving static files (HTML, CSS, JS):
{ "assets": { "directory": "public", "binding": "ASSETS", },}[assets]directory = "public"binding = "ASSETS"With a binding, you can serve assets programmatically:
export default { async fetch(request, env) { // Static assets are served by the worker automatically by default
// Route the request to the appropriate agent const agentResponse = await routeAgentRequest(request, env); if (agentResponse) return agentResponse;
// Add your own routing logic here return new Response("Not found", { status: 404 }); },};export default { async fetch(request: Request, env: Env) { // Static assets are served by the worker automatically by default
// Route the request to the appropriate agent const agentResponse = await routeAgentRequest(request, env); if (agentResponse) return agentResponse;
// Add your own routing logic here return new Response("Not found", { status: 404 }); },};For Workers AI integration:
{ "ai": { "binding": "AI", },}[ai]binding = "AI"Access in your agent:
const response = await this.env.AI.run("@cf/meta/llama-3-8b-instruct", { prompt: "Hello!",});const response = await this.env.AI.run("@cf/meta/llama-3-8b-instruct", { prompt: "Hello!",});Wrangler can generate TypeScript types for your bindings.
Run the types command:
npx wrangler typesThis creates or updates worker-configuration.d.ts with your Env type.
Specify a custom path:
npx wrangler types env.d.tsFor cleaner output (recommended for agents):
npx wrangler types env.d.ts --include-runtime falseThis generates just your bindings without Cloudflare runtime types.
// env.d.ts (generated)declare namespace Cloudflare { interface Env { OPENAI_API_KEY: string; Counter: DurableObjectNamespace; ChatAgent: DurableObjectNamespace; }}interface Env extends Cloudflare.Env {}You can also define types manually:
// env.d.ts// env.d.tsimport type { Counter } from "./src/agents/counter";import type { ChatAgent } from "./src/agents/chat";
interface Env { // Secrets OPENAI_API_KEY: string; WEBHOOK_SECRET: string;
// Agent bindings Counter: DurableObjectNamespace<Counter>; ChatAgent: DurableObjectNamespace<ChatAgent>;
// Other bindings AI: Ai; ASSETS: Fetcher; MY_KV: KVNamespace;}Add a script for easy regeneration:
{ "scripts": { "types": "wrangler types env.d.ts --include-runtime false" }}Create a .dev.vars file for local secrets (add to .gitignore):
# .dev.varsOPENAI_API_KEY=sk-...GITHUB_WEBHOOK_SECRET=whsec_...DATABASE_URL=postgres://...Access in your agent:
class MyAgent extends Agent { async onStart() { const apiKey = this.env.OPENAI_API_KEY; }}class MyAgent extends Agent { async onStart() { const apiKey = this.env.OPENAI_API_KEY; }}Use wrangler secret for production:
# Add a secretnpx wrangler secret put OPENAI_API_KEY# Enter value when prompted
# List secretsnpx wrangler secret list
# Delete a secretnpx wrangler secret delete OPENAI_API_KEYFor non-sensitive configuration, use vars in the Wrangler configuration file:
{ "vars": { "API_BASE_URL": "https://api.example.com", "MAX_RETRIES": "3", "DEBUG_MODE": "false", },}[vars]API_BASE_URL = "https://api.example.com"MAX_RETRIES = "3"DEBUG_MODE = "false"All values must be strings. Parse numbers and booleans in code:
const maxRetries = parseInt(this.env.MAX_RETRIES, 10);const debugMode = this.env.DEBUG_MODE === "true";const maxRetries = parseInt(this.env.MAX_RETRIES, 10);const debugMode = this.env.DEBUG_MODE === "true";Use env sections for different environments (for example, staging, production):
{ "name": "my-agent", "vars": { "API_URL": "https://api.example.com", },
"env": { "staging": { "vars": { "API_URL": "https://staging-api.example.com", }, }, "production": { "vars": { "API_URL": "https://api.example.com", }, }, },}name = "my-agent"
[vars]API_URL = "https://api.example.com"
[env.staging.vars]API_URL = "https://staging-api.example.com"
[env.production.vars]API_URL = "https://api.example.com"Deploy to specific environment:
npx wrangler deploy --env stagingnpx wrangler deploy --env productionWith Vite (recommended for full stack apps):
npx vite devWithout Vite:
npx wrangler devDurable Object state is persisted locally in .wrangler/state/:
Directory.wrangler/
Directorystate/
Directoryv3/
Directoryd1/
Directoryminiflare-D1DatabaseObject/
- ... (SQLite files)
To reset all local Durable Object state:
rm -rf .wrangler/stateOr restart with fresh state:
npx wrangler dev --persist-to=""You can inspect agent state directly:
# Find the SQLite filels .wrangler/state/v3/d1/
# Open with sqlite3sqlite3 .wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqliteWhen you deploy, Cloudflare automatically creates:
- Worker - Your deployed code
- Durable Object namespaces - One per agent class
- SQLite storage - Attached to each namespace
Log in to the Cloudflare dashboard, then go to Durable Objects.
Go to Durable ObjectsHere you can:
- See all Durable Object namespaces
- View individual object instances
- Inspect storage (keys and values)
- Delete objects
View live logs from your agents:
npx wrangler tailOr in the dashboard:
- Go to your Worker.
- Select the Observability tab.
- Enable real-time logs.
Filter by:
- Status (success, error)
- Search text
- Sampling rate
npx wrangler deployThis:
- Bundles your code
- Uploads to Cloudflare
- Applies migrations
- Makes it live on
*.workers.dev
Add a route in the Wrangler configuration file:
{ "routes": [ { "pattern": "agents.example.com/*", "zone_name": "example.com", }, ],}[[routes]]pattern = "agents.example.com/*"zone_name = "example.com"Or use a custom domain (simpler):
{ "routes": [ { "pattern": "agents.example.com", "custom_domain": true, }, ],}[[routes]]pattern = "agents.example.com"custom_domain = trueDeploy without affecting production:
npx wrangler deploy --dry-run # See what would be uploadednpx wrangler versions upload # Upload new versionnpx wrangler versions deploy # Gradually roll outRoll back to a previous version:
npx wrangler rollbackDefine environments in the Wrangler configuration file:
{ "name": "my-agent", "main": "src/server.ts",
// Base configuration (shared) "compatibility_date": "2025-01-01", "compatibility_flags": ["nodejs_compat"], "durable_objects": { "bindings": [{ "name": "MyAgent", "class_name": "MyAgent" }], }, "migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyAgent"] }],
// Environment overrides "env": { "staging": { "name": "my-agent-staging", "vars": { "ENVIRONMENT": "staging", }, }, "production": { "name": "my-agent-production", "vars": { "ENVIRONMENT": "production", }, }, },}name = "my-agent"main = "src/server.ts"compatibility_date = "2025-01-01"compatibility_flags = [ "nodejs_compat" ]
[[durable_objects.bindings]]name = "MyAgent"class_name = "MyAgent"
[[migrations]]tag = "v1"new_sqlite_classes = [ "MyAgent" ]
[env.staging]name = "my-agent-staging"
[env.staging.vars] ENVIRONMENT = "staging"
[env.production]name = "my-agent-production"
[env.production.vars] ENVIRONMENT = "production"# Deploy to stagingnpx wrangler deploy --env staging
# Deploy to productionnpx wrangler deploy --env production
# Set secrets per environmentnpx wrangler secret put OPENAI_API_KEY --env stagingnpx wrangler secret put OPENAI_API_KEY --env productionEach environment gets its own Durable Objects. Staging agents do not share state with production agents.
To explicitly separate:
{ "env": { "staging": { "durable_objects": { "bindings": [ { "name": "MyAgent", "class_name": "MyAgent", "script_name": "my-agent-staging", }, ], }, }, },}[[env.staging.durable_objects.bindings]]name = "MyAgent"class_name = "MyAgent"script_name = "my-agent-staging"Migrations manage Durable Object storage schema changes.
Add to new_sqlite_classes in a new migration:
{ "migrations": [ { "tag": "v1", "new_sqlite_classes": ["ExistingAgent"], }, { "tag": "v2", "new_sqlite_classes": ["NewAgent"], }, ],}[[migrations]]tag = "v1"new_sqlite_classes = [ "ExistingAgent" ]
[[migrations]]tag = "v2"new_sqlite_classes = [ "NewAgent" ]Use renamed_classes:
{ "migrations": [ { "tag": "v1", "new_sqlite_classes": ["OldName"], }, { "tag": "v2", "renamed_classes": [ { "from": "OldName", "to": "NewName", }, ], }, ],}[[migrations]]tag = "v1"new_sqlite_classes = [ "OldName" ]
[[migrations]]tag = "v2"
[[migrations.renamed_classes]] from = "OldName" to = "NewName"Also update:
- The class name in code
- The
class_namein bindings - Export statements
Use deleted_classes:
{ "migrations": [ { "tag": "v1", "new_sqlite_classes": ["AgentToDelete", "AgentToKeep"], }, { "tag": "v2", "deleted_classes": ["AgentToDelete"], }, ],}[[migrations]]tag = "v1"new_sqlite_classes = [ "AgentToDelete", "AgentToKeep" ]
[[migrations]]tag = "v2"deleted_classes = [ "AgentToDelete" ]- Never modify existing migrations - Always add new ones.
- Use sequential tags - v1, v2, v3 (or use dates: 2025-01-15).
- Test locally first - Migrations run on deploy.
- Back up production data - Before renaming or deleting.
The class is not in migrations:
{ "migrations": [ { "tag": "v1", "new_sqlite_classes": ["MissingClassName"], }, ],}[[migrations]]tag = "v1"new_sqlite_classes = [ "MissingClassName" ]Regenerate types:
npx wrangler types env.d.ts --include-runtime falseCheck that .dev.vars exists and contains the variable:
cat .dev.vars# Should show: MY_SECRET=valueMigration tags must be unique. If you see conflicts:
{ // Wrong - duplicate tags "migrations": [ { "tag": "v1", "new_sqlite_classes": ["A"] }, { "tag": "v1", "new_sqlite_classes": ["B"] }, ],}[[migrations]]tag = "v1"new_sqlite_classes = [ "A" ]
[[migrations]]tag = "v1"new_sqlite_classes = [ "B" ]{ // Correct - sequential tags "migrations": [ { "tag": "v1", "new_sqlite_classes": ["A"] }, { "tag": "v2", "new_sqlite_classes": ["B"] }, ],}[[migrations]]tag = "v1"new_sqlite_classes = [ "A" ]
[[migrations]]tag = "v2"new_sqlite_classes = [ "B" ]Was this helpful?
- Resources
- API
- New to Cloudflare?
- Directory
- Sponsorships
- Open Source
- Support
- Help Center
- System Status
- Compliance
- GDPR
- Company
- cloudflare.com
- Our team
- Careers
- © 2026 Cloudflare, Inc.
- Privacy Policy
- Terms of Use
- Report Security Issues
- Trademark
-