Skip to content

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.

Project structure

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

Wrangler configuration file

The wrangler.jsonc file configures your Cloudflare Worker and its bindings. Here is a complete example for an agents project:

JSONC
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "my-agent-app",
"main": "src/server.ts",
// Set this to today's date
"compatibility_date": "2026-07-20",
"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",
},
],
},
// Provision storage for each agent class
"exports": {
"MyAgent": {
"type": "durable-object",
"storage": "sqlite",
},
"ChatAgent": {
"type": "durable-object",
"storage": "sqlite",
},
},
// AI binding (optional, for Workers AI)
"ai": {
"binding": "AI",
},
// Observability (recommended)
"observability": {
"enabled": true,
},
}

Key fields

compatibility_flags

The nodejs_compat flag is required for agents:

JSONC
{
"compatibility_flags": ["nodejs_compat"],
}

This enables Node.js compatibility mode, which agents depend on for crypto, streams, and other Node.js APIs.

durable_objects.bindings

Each agent class needs a binding:

JSONC
{
"durable_objects": {
"bindings": [
{
"name": "Counter",
"class_name": "Counter",
},
],
},
}
FieldDescription
nameThe property name on env. Use this in code: env.Counter
class_nameMust match the exported class name exactly

exports

The exports field declares each Agent class your Worker exports and the storage backend Cloudflare should use for it:

JSONC
{
"exports": {
"MyAgent": {
"type": "durable-object",
"storage": "sqlite",
},
},
}
FieldDescription
typeThe kind of export. Always "durable-object" for an Agent.
storageThe storage backend. Use "sqlite" for new Agents (recommended).

For details on renaming, deleting, or transferring Agent classes, refer to Durable Object class exports. Existing Workers using the legacy migrations array continue to work — refer to Durable Object class migrations (legacy).

assets

For serving static files (HTML, CSS, JS):

JSONC
{
"assets": {
"directory": "public",
"binding": "ASSETS",
},
}

With a binding, you can serve assets programmatically:

JavaScript
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 });
},
};

ai

For Workers AI integration:

JSONC
{
"ai": {
"binding": "AI",
},
}

Access in your agent:

JavaScript
const response = await this.env.AI.run("@cf/meta/llama-3-8b-instruct", {
prompt: "Hello!",
});

TypeScript configuration

The Agents SDK ships a shared tsconfig.json that sets all the compiler options needed for agents projects — including the ES2021 target required for @callable() decorators, strict mode, bundler module resolution, and Workers types.

Extend it in your tsconfig.json:

{
"extends": "agents/tsconfig"
}

This is equivalent to:

{
"compilerOptions": {
"target": "ES2021",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"module": "ES2022",
"moduleResolution": "bundler",
"types": ["node", "@cloudflare/workers-types", "vite/client"],
"allowImportingTsExtensions": true,
"noEmit": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}

You can override individual options as needed:

{
"extends": "agents/tsconfig",
"compilerOptions": {
"jsx": "preserve"
}
}

Vite configuration

The Agents SDK provides a Vite plugin that handles TC39 decorator transforms. Vite 8 uses Oxc for transpilation, which does not yet support TC39 decorators — without this plugin, @callable() and other decorators will fail at runtime.

Add the plugin to your vite.config.ts:

JavaScript
import { cloudflare } from "@cloudflare/vite-plugin";
import react from "@vitejs/plugin-react";
import agents from "agents/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [agents(), react(), cloudflare()],
});

The agents() plugin is safe to include even if your project does not use decorators. It only runs the transform on files that contain @ syntax.

The starter template and all examples include this plugin by default. If you encounter SyntaxError: Invalid or unexpected token with decorators, refer to Callable methods — Troubleshooting.

Generating types

Wrangler can generate TypeScript types for your bindings.

Automatic generation

Run the types command:

Terminal window
npx wrangler types

This creates or updates worker-configuration.d.ts with your Env type.

Custom output path

Specify a custom path:

Terminal window
npx wrangler types env.d.ts

Without runtime types

For cleaner output (recommended for agents):

Terminal window
npx wrangler types env.d.ts --include-runtime false

This generates just your bindings without Cloudflare runtime types.

Example generated output

TypeScript
// env.d.ts (generated)
declare namespace Cloudflare {
interface Env {
OPENAI_API_KEY: string;
Counter: DurableObjectNamespace;
ChatAgent: DurableObjectNamespace;
}
}
interface Env extends Cloudflare.Env {}

Manual type definition

You can also define types manually:

JavaScript
// env.d.ts

Adding to package.json

Add a script for easy regeneration:

{
"scripts": {
"types": "wrangler types env.d.ts --include-runtime false"
}
}

Environment variables and secrets

Local development (.env)

Create a .env file for local secrets (add to .gitignore):

Terminal window
# .env
OPENAI_API_KEY=sk-...
GITHUB_WEBHOOK_SECRET=whsec_...
DATABASE_URL=postgres://...

Access in your agent:

JavaScript
class MyAgent extends Agent {
async onStart() {
const apiKey = this.env.OPENAI_API_KEY;
}
}

Production secrets

Use wrangler secret for production:

Terminal window
# Add a secret
npx wrangler secret put OPENAI_API_KEY
# Enter value when prompted
# List secrets
npx wrangler secret list
# Delete a secret
npx wrangler secret delete OPENAI_API_KEY

Non-secret variables

For non-sensitive configuration, use vars in the Wrangler configuration file:

JSONC
{
"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:

JavaScript
const maxRetries = parseInt(this.env.MAX_RETRIES, 10);
const debugMode = this.env.DEBUG_MODE === "true";

Environment-specific variables

Use env sections for different environments (for example, staging, production):

JSONC
{
"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",
},
},
},
}

Deploy to specific environment:

Terminal window
npx wrangler deploy --env staging
npx wrangler deploy --env production

Local development

Starting the dev server

With Vite (recommended for full stack apps):

Terminal window
npx vite dev

Without Vite:

Terminal window
npx wrangler dev

Local state persistence

Durable Object state is persisted locally in .wrangler/state/:

  • Directory.wrangler/
    • Directorystate/
      • Directoryv3/
        • Directoryd1/
          • Directoryminiflare-D1DatabaseObject/
            • ... (SQLite files)

Clearing local state

To reset all local Durable Object state:

Terminal window
rm -rf .wrangler/state

Or restart with fresh state:

Terminal window
npx wrangler dev --persist-to=""

Inspecting local SQLite

You can inspect agent state directly:

Terminal window
# Find the SQLite file
ls .wrangler/state/v3/d1/
# Open with sqlite3
sqlite3 .wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite

Dashboard setup

Automatic resources

When you deploy, Cloudflare automatically creates:

  • Worker - Your deployed code
  • Durable Object namespaces - One per agent class
  • SQLite storage - Attached to each namespace

Viewing Durable Objects

Log in to the Cloudflare dashboard, then go to Durable Objects.

Go to Durable Objects

Here you can:

  • See all Durable Object namespaces
  • View individual object instances
  • Inspect storage (keys and values)
  • Delete objects

Real-time logs

View live logs from your agents:

Terminal window
npx wrangler tail

Or in the dashboard:

  1. Go to your Worker.
  2. Select the Observability tab.
  3. Enable real-time logs.

Filter by:

  • Status (success, error)
  • Search text
  • Sampling rate

Production deployment

Basic deploy

Terminal window
npx wrangler deploy

This:

  1. Bundles your code
  2. Uploads to Cloudflare
  3. Provisions the Agent's Durable Object namespace storage
  4. Makes it live on *.workers.dev

Custom domain

Add a route in the Wrangler configuration file:

JSONC
{
"routes": [
{
"pattern": "agents.example.com/*",
"zone_name": "example.com",
},
],
}

Or use a custom domain (simpler):

JSONC
{
"routes": [
{
"pattern": "agents.example.com",
"custom_domain": true,
},
],
}

Preview deployments

Deploy without affecting production:

Terminal window
npx wrangler deploy --dry-run # See what would be uploaded
npx wrangler versions upload # Upload new version
npx wrangler versions deploy # Gradually roll out

Rollbacks

Roll back to a previous version:

Terminal window
npx wrangler rollback

Multi-environment setup

Environment configuration

Define environments in the Wrangler configuration file:

JSONC
{
"name": "my-agent",
"main": "src/server.ts",
// Base configuration (shared)
// Set this to today's date
"compatibility_date": "2026-07-20",
"compatibility_flags": ["nodejs_compat"],
"durable_objects": {
"bindings": [{ "name": "MyAgent", "class_name": "MyAgent" }],
},
"exports": {
"MyAgent": { "type": "durable-object", "storage": "sqlite" },
},
// Environment overrides
"env": {
"staging": {
"name": "my-agent-staging",
"vars": {
"ENVIRONMENT": "staging",
},
},
"production": {
"name": "my-agent-production",
"vars": {
"ENVIRONMENT": "production",
},
},
},
}

Deploying to environments

Terminal window
# Deploy to staging
npx wrangler deploy --env staging
# Deploy to production
npx wrangler deploy --env production
# Set secrets per environment
npx wrangler secret put OPENAI_API_KEY --env staging
npx wrangler secret put OPENAI_API_KEY --env production

Separate Durable Objects

Each environment gets its own Durable Objects. Staging agents do not share state with production agents.

To explicitly separate:

JSONC
{
"env": {
"staging": {
"durable_objects": {
"bindings": [
{
"name": "MyAgent",
"class_name": "MyAgent",
"script_name": "my-agent-staging",
},
],
},
},
},
}

Agent class lifecycle

Each Agent maps to a Durable Object class. You manage the lifecycle of those classes (create, rename, delete, transfer) through the exports field of your Wrangler configuration file.

Adding a new agent

Declare the new class in the exports:

JSONC
{
"exports": {
"NewAgent": { "type": "durable-object", "storage": "sqlite" },
},
}

Renaming an agent class

Replace the entry for the old name with a renamed tombstone and add a live entry for the new name:

JSONC
{
"exports": {
"OldName": {
"type": "durable-object",
"state": "renamed",
"renamed_to": "NewName",
},
"NewName": { "type": "durable-object", "storage": "sqlite" },
},
}

Also update:

  1. The class name in code.
  2. The class_name in bindings.
  3. Export statements.

Deleting an agent class

Replace the entry with a deleted tombstone:

JSONC
{
"exports": {
"AgentToKeep": { "type": "durable-object", "storage": "sqlite" },
"AgentToDelete": { "type": "durable-object", "state": "deleted" },
},
}

Class lifecycle best practices

  1. Keep exports in sync with your code. Every Agent class your Worker exports needs an entry.
  2. Use tombstones, not silent removal. When retiring a class, leave a deleted / renamed / transferred tombstone in exports so Cloudflare reconciles the change explicitly.
  3. Test locally first. Lifecycle changes apply on wrangler deploy.
  4. Back up production data before renaming or deleting.

Existing Workers using the legacy migrations array continue to work — refer to Durable Object class migrations (legacy) for the legacy reference, or Migrate from the legacy migrations flow to move to exports.

Troubleshooting

No such Durable Object class

The class is missing from exports. Declare the class and its storage:

JSONC
{
"exports": {
"MissingClassName": { "type": "durable-object", "storage": "sqlite" },
},
}

Cannot find module in types

Regenerate types:

Terminal window
npx wrangler types env.d.ts --include-runtime false

Secrets not loading locally

Check that .env exists and contains the variable:

Terminal window
cat .env
# Should show: MY_SECRET=value

Migration tag conflict (legacy migrations only)

If your Worker uses the legacy migrations array, each entry must have a unique tag:

JSONC
{
// Wrong - duplicate tags
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["A"] },
{ "tag": "v1", "new_sqlite_classes": ["B"] },
],
}
JSONC
{
// Correct - sequential tags
"migrations": [
{ "tag": "v1", "new_sqlite_classes": ["A"] },
{ "tag": "v2", "new_sqlite_classes": ["B"] },
],
}

Consider converting to the declarative exports field.

Next steps