Routing (2)·续1

When identity is disabled:

  • agent.identified stays false
  • agent.ready never resolves (use state updates instead)
  • onIdentity and onIdentityChange are never called

When to use custom routing

Scenario Approach
Standard agent access Default /agents/{agent}/{name}
Instance from auth/session basePath + getAgentByName + fetch
Clean URLs (no /agents/ prefix) basePath + custom routing
Legacy URL structure basePath + custom routing
Complex routing logic Custom routing in Worker

Routing options

Both routeAgentRequest() and getAgentByName() accept options for customizing routing behavior.

CORS

For cross-origin requests (common when your frontend is on a different domain):

const response = await routeAgentRequest(request, env, {
    cors: true, // Enable default CORS headers
});
const response = await routeAgentRequest(request, env, {
    cors: true, // Enable default CORS headers
});

Or with custom CORS headers:

const response = await routeAgentRequest(request, env, {
    cors: {
        "Access-Control-Allow-Origin": "https://myapp.com",
        "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
        "Access-Control-Allow-Headers": "Content-Type, Authorization",
    },
});
const response = await routeAgentRequest(request, env, {
    cors: {
        "Access-Control-Allow-Origin": "https://myapp.com",
        "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
        "Access-Control-Allow-Headers": "Content-Type, Authorization",
    },
});

Location hints

For latency-sensitive applications, hint where the agent should run:

// With getAgentByName
const agent = await getAgentByName(env.MyAgent, "instance-name", {
    locationHint: "enam", // Eastern North America
});

// With routeAgentRequest (applies to all matched agents)
const response = await routeAgentRequest(request, env, {
    locationHint: "enam",
});
// With getAgentByName
const agent = await getAgentByName(env.MyAgent, "instance-name", {
    locationHint: "enam", // Eastern North America
});

// With routeAgentRequest (applies to all matched agents)
const response = await routeAgentRequest(request, env, {
    locationHint: "enam",
});

Available location hints: wnam, enam, sam, weur, eeur, apac, oc, afr, me

Jurisdiction

For data residency requirements:

// With getAgentByName
const agent = await getAgentByName(env.MyAgent, "instance-name", {
    jurisdiction: "eu", // EU jurisdiction
});

// With routeAgentRequest (applies to all matched agents)
const response = await routeAgentRequest(request, env, {
    jurisdiction: "eu",
});
// With getAgentByName
const agent = await getAgentByName(env.MyAgent, "instance-name", {
    jurisdiction: "eu", // EU jurisdiction
});

// With routeAgentRequest (applies to all matched agents)
const response = await routeAgentRequest(request, env, {
    jurisdiction: "eu",
});

Props

Since agents are instantiated by the runtime rather than constructed directly, props provides a way to pass initialization arguments:

const agent = await getAgentByName(env.MyAgent, "instance-name", {
    props: {
        userId: session.userId,
        config: { maxRetries: 3 },
    },
});
const agent = await getAgentByName(env.MyAgent, "instance-name", {
    props: {
        userId: session.userId,
        config: { maxRetries: 3 },
    },
});

Props are passed to the agent's onStart lifecycle method:

class MyAgent extends Agent {
    userId;
    config;

async onStart(props) {
        this.userId = props?.userId;
        this.config = props?.config;
    }
}
class MyAgent extends Agent<Env, State> {
    private userId?: string;
    private config?: { maxRetries: number };

async onStart(props?: { userId: string; config: { maxRetries: number } }) {
        this.userId = props?.userId;
        this.config = props?.config;
    }
}

When using props with routeAgentRequest, the same props are passed to whichever agent matches the URL. This works well for universal context like authentication:

export default {
    async fetch(request, env) {
        const session = await getSession(request);
        return routeAgentRequest(request, env, {
            props: { userId: session.userId, role: session.role },
        });
    },
};
export default {
    async fetch(request, env) {
        const session = await getSession(request);
        return routeAgentRequest(request, env, {
            props: { userId: session.userId, role: session.role },
        });
    },
} satisfies ExportedHandler<Env>;

For agent-specific initialization, use getAgentByName instead where you control exactly which agent receives the props.

Note

For McpAgent, props are automatically stored and accessible via this.props. Refer to MCP servers for details.

Routing retry

Use routingRetry with getAgentByName() when server-side code should retry transient Durable Object routing failures:

const agent = await getAgentByName(env.MyAgent, "instance-name", {
    routingRetry: {
        maxAttempts: 3,
    },
});
const agent = await getAgentByName(env.MyAgent, "instance-name", {
    routingRetry: {
        maxAttempts: 3,
    },
});

This option is useful for request forwarding and RPC paths where a short-lived routing failure should be retried before returning an error to the caller.

Hooks

routeAgentRequest supports hooks for intercepting requests before they reach agents:

const response = await routeAgentRequest(request, env, {
    onBeforeConnect: (req, lobby) => {
        // Called before WebSocket connections
        // Return a Response to reject, Request to modify, or void to continue
    },
    onBeforeRequest: (req, lobby) => {
        // Called before HTTP requests
        // Return a Response to reject, Request to modify, or void to continue
    },
});
const response = await routeAgentRequest(request, env, {
    onBeforeConnect: (req, lobby) => {
        // Called before WebSocket connections
        // Return a Response to reject, Request to modify, or void to continue
    },
    onBeforeRequest: (req, lobby) => {
        // Called before HTTP requests
        // Return a Response to reject, Request to modify, or void to continue
    },
});

These hooks are useful for authentication and validation. Refer to Cross-domain authentication for detailed examples.

Server-side agent access

You can access agents from your Worker code using getAgentByName() for RPC calls:

import { getAgentByName, routeAgentRequest } from "agents";

export default {
    async fetch(request, env) {
        const url = new URL(request.url);

// API endpoint that interacts with an agent
        if (url.pathname === "/api/increment") {
            const counter = await getAgentByName(env.Counter, "global-counter");
            const newCount = await counter.increment();
            return Response.json({ count: newCount });
        }

// Regular agent routing
        return (
            (await routeAgentRequest(request, env)) ??
            new Response("Not found", { status: 404 })
        );
    },
};
import { getAgentByName, routeAgentRequest } from "agents";

export default {
    async fetch(request: Request, env: Env) {
        const url = new URL(request.url);

// API endpoint that interacts with an agent
        if (url.pathname === "/api/increment") {
            const counter = await getAgentByName(env.Counter, "global-counter");
            const newCount = await counter.increment();
            return Response.json({ count: newCount });
        }

// Regular agent routing
        return (
            (await routeAgentRequest(request, env)) ??
            new Response("Not found", { status: 404 })
        );
    },
} satisfies ExportedHandler<Env>;

For options like locationHint, jurisdiction, and props, refer to Routing options.

Sub-paths and HTTP methods

Requests can include sub-paths after the instance name. These are passed to your agent's onRequest() handler:

/agents/api/v1/users     → agent: "api", instance: "v1", path: "/users"
/agents/api/v1/users/123 → agent: "api", instance: "v1", path: "/users/123"

Handle sub-paths in your agent:

export class API extends Agent {
    async onRequest(request) {
        const url = new URL(request.url);

// url.pathname contains the full path including /agents/api/v1/...
        // Extract the sub-path after your agent's base path
        const path = url.pathname.replace(/^\/agents\/api\/[^/]+/, "");

if (request.method === "GET" && path === "/users") {
            return Response.json(await this.getUsers());
        }

if (request.method === "POST" && path === "/users") {
            const data = await request.json();
            return Response.json(await this.createUser(data));
        }

return new Response("Not found", { status: 404 });
    }
}
export class API extends Agent {
    async onRequest(request: Request): Promise<Response> {
        const url = new URL(request.url);

// url.pathname contains the full path including /agents/api/v1/...
        // Extract the sub-path after your agent's base path
        const path = url.pathname.replace(/^\/agents\/api\/[^/]+/, "");

if (request.method === "GET" && path === "/users") {
            return Response.json(await this.getUsers());
        }

if (request.method === "POST" && path === "/users") {
            const data = await request.json();
            return Response.json(await this.createUser(data));
        }

return new Response("Not found", { status: 404 });
    }
}

Multiple agents

You can have multiple agent classes in one project. Each gets its own namespace:

// server.ts
export { Counter } from "./agents/counter";
export { ChatRoom } from "./agents/chat-room";
export { UserProfile } from "./agents/user-profile";

export default {
    async fetch(request, env) {
        return (
            (await routeAgentRequest(request, env)) ??
            new Response("Not found", { status: 404 })
        );
    },
};
// server.ts
export { Counter } from "./agents/counter";
export { ChatRoom } from "./agents/chat-room";
export { UserProfile } from "./agents/user-profile";

export default {
    async fetch(request: Request, env: Env) {
        return (
            (await routeAgentRequest(request, env)) ??
            new Response("Not found", { status: 404 })
        );
    },
} satisfies ExportedHandler<Env>;
{
    "durable_objects": {
        "bindings": [
            { "name": "Counter", "class_name": "Counter" },
            { "name": "ChatRoom", "class_name": "ChatRoom" },
            { "name": "UserProfile", "class_name": "UserProfile" },
        ],
    },
    "migrations": [
        {
            "tag": "v1",
            "new_sqlite_classes": ["Counter", "ChatRoom", "UserProfile"],
        },
    ],
}
[[durable_objects.bindings]]
name = "Counter"
class_name = "Counter"

[[durable_objects.bindings]]
name = "ChatRoom"
class_name = "ChatRoom"

[[durable_objects.bindings]]
name = "UserProfile"
class_name = "UserProfile"

[[migrations]]
tag = "v1"
new_sqlite_classes = [ "Counter", "ChatRoom", "UserProfile" ]

Each agent is accessed via its own path:

/agents/counter/...
/agents/chat-room/...
/agents/user-profile/...

Request flow

Here is how a request flows through the system:

flowchart TD
    A["HTTP Request<br/>or WebSocket"] --> B["routeAgentRequest<br/>Parse URL path"]
    B --> C["Find binding in<br/>env by name"]
    C --> D["Get/create DO<br/>by instance ID"]
    D --> E["Agent Instance"]
    E --> F{"Protocol?"}
    F -->|WebSocket| G["onConnect(), onMessage"]
    F -->|HTTP| H["onRequest()"]

Routing with authentication

There are several ways to authenticate requests before they reach your agent.

Using authentication hooks

The routeAgentRequest() function provides onBeforeConnect and onBeforeRequest hooks for authentication:

import { Agent, routeAgentRequest } from "agents";

export default {
    async fetch(request, env) {
        return (
            (await routeAgentRequest(request, env, {
                // Run before WebSocket connections
                onBeforeConnect: async (request) => {
                    const token = new URL(request.url).searchParams.get("token");
                    if (!(await verifyToken(token, env))) {
                        // Return a response to reject the connection
                        return new Response("Unauthorized", { status: 401 });
                    }
                    // Return nothing to allow the connection
                },
                // Run before HTTP requests
                onBeforeRequest: async (request) => {
                    const auth = request.headers.get("Authorization");
                    if (!auth || !(await verifyAuth(auth, env))) {
                        return new Response("Unauthorized", { status: 401 });
                    }
                },
                // Optional: prepend a prefix to agent instance names
                prefix: "user-",
            })) ?? new Response("Not found", { status: 404 })
        );
    },
};