diff --git a/src/content/changelog/browser-run/2026-09-21-browser-binding-methods.mdx b/src/content/changelog/browser-run/2026-09-21-browser-binding-methods.mdx
new file mode 100644
index 00000000000..fa0c2ff3bd8
--- /dev/null
+++ b/src/content/changelog/browser-run/2026-09-21-browser-binding-methods.mdx
@@ -0,0 +1,31 @@
+---
+title: Browser Run adds session and DevTools methods to browser bindings
+description: Browser Run browser bindings now support typed session management, outbound Worker routing, and DevTools methods.
+products:
+ - browser-run
+date: 2026-09-21
+---
+
+import { TypeScriptExample } from "~/components";
+
+[Browser Run](/browser-run/) browser bindings now provide typed methods for session management and DevTools operations. You can acquire a session, connect a browser client, create Live View URLs, manage targets, and close sessions without constructing HTTP requests.
+
+The new `acquire()` and `launch()` methods also accept [`outboundByHost`](/browser-run/features/outbound-workers/). This lets you route requests for selected hostnames through another Worker, including a Worker that adds authentication or reaches a private service.
+
+
+
+```ts
+const connection = await env.BROWSER.launch({
+ outboundByHost: {
+ "private.example.test": env.OUTBOUND,
+ },
+});
+```
+
+
+
+Use `connectSession(sessionId)` when you need to acquire and connect in separate steps. The method returns a session-pinned `webSocket` Fetcher for a CDP client.
+
+The binding also includes session methods for Live View, active sessions, session history, limits, session details, and cleanup. The nested `devtools` binding provides typed methods for browser version information, protocol descriptions, and target operations such as listing, creating, activating, and closing targets.
+
+Refer to the [Browser binding API documentation](/browser-run/reference/browser-binding-api/) for method signatures and the [outbound Worker feature guide](/browser-run/features/outbound-workers/) for routing examples.
diff --git a/src/content/docs/browser-run/cdp/session-management.mdx b/src/content/docs/browser-run/cdp/session-management.mdx
index bc11a3b70fa..82e31d51f67 100644
--- a/src/content/docs/browser-run/cdp/session-management.mdx
+++ b/src/content/docs/browser-run/cdp/session-management.mdx
@@ -16,6 +16,8 @@ Use the HTTP API to manage browser sessions and tabs without using WebSocket con
The [API reference](/api/resources/browser_rendering/) documents all session management endpoints under `/devtools`.
+When your code runs in a Worker, you can use the typed [Browser binding API](/browser-run/reference/browser-binding-api/) instead of constructing HTTP requests. The binding exposes `acquire()`, `connectSession()`, `launch()`, and a nested `devtools` target for session and target management.
+
## Step 1: Acquire a browser session
Create a new browser session using the `POST /devtools/browser` endpoint. The session will remain active for the specified keep-alive time (in this example, 10 minutes).
diff --git a/src/content/docs/browser-run/features/outbound-workers.mdx b/src/content/docs/browser-run/features/outbound-workers.mdx
new file mode 100644
index 00000000000..418e322aa0e
--- /dev/null
+++ b/src/content/docs/browser-run/features/outbound-workers.mdx
@@ -0,0 +1,156 @@
+---
+pcx_content_type: how-to
+title: Outbound Workers
+description: Route selected Browser Run requests through another Worker for private services, authentication, or response processing.
+sidebar:
+ order: 8
+products:
+ - browser-run
+---
+
+import { TypeScriptExample, WranglerConfig } from "~/components";
+
+Use `outboundByHost` to send browser requests for selected hostnames through another Worker. The outbound Worker can reach a private service, add authentication, or transform the response before it reaches the browser.
+
+This feature is useful when the browser should request a hostname that has no public DNS record. Browser Run matches the hostname and sends the request to the Worker Fetcher instead of requiring public DNS resolution.
+
+## Configure the bindings
+
+Declare a Browser Run binding and a service binding for the outbound Worker:
+
+
+
+```toml
+name = "browser-runner"
+main = "src/index.ts"
+compatibility_date = "$today"
+compatibility_flags = ["nodejs_compat"]
+
+[browser]
+binding = "BROWSER"
+
+[[services]]
+binding = "OUTBOUND"
+service = "outbound-worker"
+```
+
+
+
+The service binding gives the browser Worker a `Fetcher` for the outbound Worker. The outbound Worker does not need a public route.
+
+## Route requests by hostname
+
+Pass the service binding to `launch()` or `acquire()` in the `outboundByHost` map. The map key must exactly match the hostname in the browser request.
+
+The following example uses raw CDP commands. It opens a Browser Run session, routes `private.example.test` through the outbound Worker, and sends a `Page.navigate` command:
+
+
+
+```ts
+interface Env {
+ BROWSER: Fetcher;
+ OUTBOUND: Fetcher;
+}
+
+type CdpResponse = {
+ id: number;
+ result?: unknown;
+ error?: { message: string };
+};
+
+let nextCdpCommandId = 0;
+
+function sendCdpCommand(
+ socket: WebSocket,
+ method: string,
+ params: Record = {},
+): Promise {
+ return new Promise((resolve, reject) => {
+ const id = ++nextCdpCommandId;
+ const timeout = setTimeout(() => {
+ cleanup();
+ reject(new Error(`CDP command "${method}" timed out`));
+ }, 30_000);
+
+ const cleanup = () => {
+ clearTimeout(timeout);
+ socket.removeEventListener("message", onMessage);
+ socket.removeEventListener("close", onClose);
+ };
+
+ const onClose = () => {
+ cleanup();
+ reject(new Error("CDP connection closed before the command completed"));
+ };
+
+ const onMessage = (event: MessageEvent) => {
+ const message = JSON.parse(event.data) as CdpResponse;
+ if (message.id !== id) return;
+
+ cleanup();
+ if (message.error) {
+ reject(new Error(message.error.message));
+ } else {
+ resolve(message.result);
+ }
+ };
+
+ socket.addEventListener("message", onMessage);
+ socket.addEventListener("close", onClose);
+ try {
+ socket.send(JSON.stringify({ id, method, params }));
+ } catch (error) {
+ cleanup();
+ reject(error);
+ }
+ });
+}
+
+export default {
+ async fetch(_request: Request, env: Env): Promise {
+ const connection = await env.BROWSER.launch({
+ outboundByHost: {
+ "private.example.test": env.OUTBOUND,
+ },
+ });
+ const response = await connection.webSocket.fetch(
+ "https://browser-binding.invalid",
+ { headers: { Upgrade: "websocket" } },
+ );
+ if (!response.webSocket) {
+ throw new Error("Browser Run did not return a WebSocket");
+ }
+
+ const socket = response.webSocket;
+ socket.accept();
+
+ try {
+ await sendCdpCommand(socket, "Page.navigate", {
+ url: "http://private.example.test",
+ });
+ return new Response("Navigation sent through the outbound Worker");
+ } finally {
+ socket.close();
+ await env.BROWSER.closeSession(connection.sessionId);
+ }
+ },
+};
+```
+
+
+
+The `.test` top-level domain is reserved for testing. Use a hostname that does not resolve on public DNS for a virtual outbound route. The hostname must still match the key in `outboundByHost`.
+
+Outbound Worker routing supports HTTP requests only. Use an `http://` URL for the routed hostname. HTTPS requests do not use the outbound Worker.
+
+## Constraints
+
+- Use `outboundByHost` with Browser Run binding methods only. It is not supported by REST endpoints or legacy HTTP-only bindings.
+- Create the Fetcher and call `acquire()` or `launch()` in the same Worker invocation.
+- The Fetcher is not persisted with the browser session and cannot be reused by a later invocation.
+- Outbound routing applies to the hostnames in the map. Requests to other hostnames use the browser's normal network path.
+
+## Next steps
+
+- Review the [Browser binding API](/browser-run/reference/browser-binding-api/).
+- Learn how to use the [Chrome DevTools Protocol](/browser-run/cdp/).
diff --git a/src/content/docs/browser-run/reference/browser-binding-api.mdx b/src/content/docs/browser-run/reference/browser-binding-api.mdx
new file mode 100644
index 00000000000..5e45bd4a03a
--- /dev/null
+++ b/src/content/docs/browser-run/reference/browser-binding-api.mdx
@@ -0,0 +1,243 @@
+---
+pcx_content_type: reference
+title: Browser binding API
+description: Acquire, connect to, inspect, and manage Browser Run sessions from a Cloudflare Worker with typed browser binding methods.
+sidebar:
+ order: 22
+products:
+ - browser-run
+---
+
+import { TypeScriptExample, WranglerConfig } from "~/components";
+
+The Browser Run binding provides typed methods for browser session management from a [Cloudflare Worker](/workers/). Use these methods to acquire a session, connect a browser client, route browser requests through another Worker, and manage DevTools targets.
+
+Configure a [browser binding](/browser-run/reference/wrangler/#bindings) in your Wrangler configuration:
+
+
+
+```toml
+name = "browser-binding-example"
+main = "src/index.ts"
+compatibility_date = "$today"
+compatibility_flags = ["nodejs_compat"]
+
+[browser]
+binding = "BROWSER"
+```
+
+
+
+## Session methods
+
+| Method | Description |
+| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
+| `acquire(options?)` | Creates a browser session and returns its session ID. Set `targets: true` to include the session's current targets. |
+| `connectSession(sessionId, options?)` | Returns a connection object with a session-pinned `webSocket` Fetcher for connecting a CDP client to an existing session. |
+| `launch(options?)` | Acquires a session and returns the same connection result as `connectSession()`. |
+| `getLiveView(sessionId, options?)` | Creates a Live View URL for a session or target. |
+| `listSessions(options?)` | Lists active sessions in the account. |
+| `history(options?)` | Lists recent active and closed sessions. |
+| `limits()` | Returns the current session limits and usage values. |
+| `getSession(sessionId)` | Returns session details or `null` when the session does not exist. |
+| `closeSession(sessionId)` | Closes a session and returns `closing` or `closed`. |
+| `devtools` | Provides typed methods for the DevTools JSON endpoints. |
+
+## Acquire and connect to a session
+
+`acquire()` returns session metadata. Use `connectSession()` to get a session-pinned Fetcher, then open a WebSocket upgrade and send Chrome DevTools Protocol (CDP) commands.
+
+
+
+```ts
+interface Env {
+ BROWSER: Fetcher;
+}
+
+type CdpResponse = {
+ id: number;
+ result?: unknown;
+ error?: { message: string };
+};
+
+let nextCdpCommandId = 0;
+
+function sendCdpCommand(
+ socket: WebSocket,
+ method: string,
+ params: Record = {},
+): Promise {
+ return new Promise((resolve, reject) => {
+ const id = ++nextCdpCommandId;
+ const timeout = setTimeout(() => {
+ cleanup();
+ reject(new Error(`CDP command "${method}" timed out`));
+ }, 30_000);
+
+ const cleanup = () => {
+ clearTimeout(timeout);
+ socket.removeEventListener("message", onMessage);
+ socket.removeEventListener("close", onClose);
+ };
+
+ const onClose = () => {
+ cleanup();
+ reject(new Error("CDP connection closed before the command completed"));
+ };
+
+ const onMessage = (event: MessageEvent) => {
+ const message = JSON.parse(event.data) as CdpResponse;
+ if (message.id !== id) return;
+
+ cleanup();
+ if (message.error) {
+ reject(new Error(message.error.message));
+ } else {
+ resolve(message.result);
+ }
+ };
+
+ socket.addEventListener("message", onMessage);
+ socket.addEventListener("close", onClose);
+ try {
+ socket.send(JSON.stringify({ id, method, params }));
+ } catch (error) {
+ cleanup();
+ reject(error);
+ }
+ });
+}
+
+export default {
+ async fetch(request: Request, env: Env): Promise {
+ const session = await env.BROWSER.acquire({ targets: true });
+ const connection = await env.BROWSER.connectSession(session.sessionId);
+ const response = await connection.webSocket.fetch(
+ "https://browser-binding.invalid",
+ { headers: { Upgrade: "websocket" } },
+ );
+ if (!response.webSocket) {
+ throw new Error("Browser Run did not return a WebSocket");
+ }
+
+ const socket = response.webSocket;
+ socket.accept();
+
+ try {
+ await sendCdpCommand(socket, "Page.navigate", {
+ url: "https://example.com",
+ });
+ const version = await sendCdpCommand(socket, "Browser.getVersion");
+ return Response.json(version);
+ } finally {
+ socket.close();
+ await env.BROWSER.closeSession(session.sessionId);
+ }
+ },
+};
+```
+
+
+
+Use `launch()` when you do not need to separate acquisition from connection:
+
+
+
+```ts
+interface Env {
+ BROWSER: Fetcher;
+}
+
+async function useLaunch(env: Env): Promise {
+ const session = await env.BROWSER.launch();
+ const response = await session.webSocket.fetch(
+ "https://browser-binding.invalid",
+ { headers: { Upgrade: "websocket" } },
+ );
+ if (!response.webSocket) {
+ throw new Error("Browser Run did not return a WebSocket");
+ }
+
+ const socket = response.webSocket;
+ socket.accept();
+
+ try {
+ return await sendCdpCommand(socket, "Browser.getVersion");
+ } finally {
+ socket.close();
+ await env.BROWSER.closeSession(session.sessionId);
+ }
+}
+```
+
+
+
+`launch()` performs the acquire and connection-capability steps in one call. Both methods return a connection object with this shape:
+
+| Property | Description |
+| ----------- | --------------------------------------------------------- |
+| `sessionId` | The Browser Run session ID. |
+| `webSocket` | A session-pinned `Fetcher` used to open a CDP connection. |
+| `targets` | The target list when requested with `targets: true`. |
+
+### Session options
+
+`acquire()` and `launch()` accept these options:
+
+| Option | Type | Description |
+| ------------------------ | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `keepAlive` | `number` | Session inactivity timeout in milliseconds. The value must be between 10 seconds and 20 minutes. |
+| `recording` | `boolean` | Records the session for later inspection. |
+| `location` | `string` | ISO 3166-1 alpha-2 country code for the browser location. |
+| `outboundByHost` | `Record` | Routes requests for each hostname through a Worker Fetcher. Refer to [Route requests through an outbound Worker](/browser-run/features/outbound-workers/). |
+| `guardrails` | `object` | Restricts the hostnames that the browser session can access. Refer to [Guardrails](/browser-run/features/guardrails/). |
+| `targets` | `boolean` | Includes the session's DevTools targets in the result. |
+| `liveViewUrlExpiresInMs` | `number` | Sets the expiry for target Live View URLs when `targets` is `true`. |
+
+The binding uses `keepAlive` in its options object.
+
+## DevTools methods
+
+The `devtools` property exposes the DevTools JSON endpoints as typed methods. It is one nested binding target, so you can call the methods from the same Browser Run binding:
+
+| Method | Description |
+| -------------------------------------- | ---------------------------------------------------------------------------------------------------- |
+| `getVersion(sessionId)` | Returns browser version information. |
+| `getProtocol(sessionId)` | Returns the browser's DevTools protocol description. |
+| `listTargets(sessionId, options?)` | Lists the browser's targets. Set `liveViewUrlExpiresInMs` to control generated Live View URL expiry. |
+| `getTarget(sessionId, targetId)` | Returns one target. |
+| `newTarget(sessionId, url?, options?)` | Opens a new target. If `url` is omitted, the target opens at `about:blank`. |
+| `activateTarget(sessionId, targetId)` | Activates a target. |
+| `closeTarget(sessionId, targetId)` | Closes a target. |
+
+Target objects include the target ID, type, URL, title, and, when available, a `devtoolsFrontendUrl`.
+
+
+
+```ts
+const targets = await env.BROWSER.devtools.listTargets(sessionId, {
+ liveViewUrlExpiresInMs: 300_000,
+});
+
+const page = targets.find((target) => target.type === "page");
+if (!page) {
+ throw new Error("No page target found");
+}
+
+const liveView = await env.BROWSER.getLiveView(sessionId, {
+ targetId: page.id,
+ mode: "devtools",
+});
+
+console.log(liveView.devtoolsFrontendUrl);
+```
+
+
+
+Use the returned `devtoolsFrontendUrl` to open Live View. To connect a CDP client to a specific target, pass its ID to `connectSession(sessionId, { targetId })`. Treat Live View URLs as credentials because they contain access tokens.
+
+## Next steps
+
+- Learn about [Live View](/browser-run/features/live-view/).
+- Review [session management with HTTP](/browser-run/cdp/session-management/).
+- Use the [Chrome DevTools Protocol](/browser-run/cdp/) for direct browser control.
diff --git a/src/content/docs/browser-run/reference/wrangler.mdx b/src/content/docs/browser-run/reference/wrangler.mdx
index 87b1b4f839b..07839031300 100644
--- a/src/content/docs/browser-run/reference/wrangler.mdx
+++ b/src/content/docs/browser-run/reference/wrangler.mdx
@@ -50,6 +50,8 @@ After the binding is declared, access the DevTools endpoint using `env.MYBROWSER
const browser = await puppeteer.launch(env.MYBROWSER);
```
+Browser bindings also provide typed methods for [session management, outbound Worker routing, and DevTools operations](/browser-run/reference/browser-binding-api/).
+
:::note[Quick Actions compatibility]
The browser binding's `.quickAction()` method requires a compatibility date of `2026-03-24` or later. Ensure your `wrangler.json` includes:
diff --git a/src/content/release-notes/browser-run.yaml b/src/content/release-notes/browser-run.yaml
index 1273512de7f..091db406356 100644
--- a/src/content/release-notes/browser-run.yaml
+++ b/src/content/release-notes/browser-run.yaml
@@ -3,6 +3,11 @@ link: "/browser-run/changelog/"
productName: Browser Run
productLink: "/browser-run/"
entries:
+ - publish_date: "2026-09-21"
+ title: "Browser binding session and DevTools methods"
+ description: |-
+ * Browser Run browser bindings now support typed methods for [acquiring and connecting to sessions](/browser-run/reference/browser-binding-api/), launching a browser with [outbound Worker routing](/browser-run/features/outbound-workers/), creating Live View URLs, and managing session lifecycle.
+ * Added a nested `devtools` binding with methods for browser version and protocol information, listing and creating targets, and activating or closing targets. See the [Browser binding API reference](/browser-run/reference/browser-binding-api/) for the full API.
- publish_date: "2026-07-28"
title: "Structured handoff for Human in the Loop"
description: |-