Stagehand integration
Stagehand is an open-source SDK for browser agents that mixes natural-language actions (act(), observe(), extract()) with Playwright-style page code. This guide connects Stagehand v4 to a Browserless cloud browser, so you can run Stagehand automations without hosting Chrome yourself.
- Node.js 22.18 or later (required by Stagehand v4)
- Browserless API token (available in your account dashboard)
- An Anthropic or OpenAI API key (Stagehand also supports Google, Groq, and Cerebras)
- Familiarity with Browserless browser extensions
How Stagehand v4 connects to Browserless
Stagehand v4 runs its engine as a Chrome extension inside the browser, and that extension opens its own CDP connection back to the browser. That changes two things compared with Stagehand v3:
- The extension has to be preinstalled.
localBrowser.connect()tries to load the extension from a folder on your machine, which a remote browser can't read. You upload the extension to Browserless once and load it with theextensionslaunch parameter. - The extension has to dial the same session. A regular Browserless WebSocket URL starts a new browser on every connection, so the extension would end up in a different browser from your script. You call
Browserless.reconnectto get a URL for the current session, then pass it to Stagehand as bothcdpUrlandextensionCdpUrl.
If you'd rather skip both steps, see Using Stagehand v3.
Step-by-step setup
Set your API keys
Go to your Browserless account dashboard and copy your API token. Then set it in a
.envfile or your shell, with one LLM key. The script uses whichever key is set, and prefers Anthropic if both are.- Anthropic
- OpenAI
BROWSERLESS_API_KEY=your-browserless-token
ANTHROPIC_API_KEY=your-anthropic-keyBROWSERLESS_API_KEY=your-browserless-token
OPENAI_API_KEY=your-openai-keyInstall dependencies
Stagehand v4 pins Zod 4.4. You'll also need
puppeteer-coreto callBrowserless.reconnect.- npm
- pnpm
npm install @browserbasehq/stagehand 'zod@~4.4.3' puppeteer-corepnpm add @browserbasehq/stagehand 'zod@~4.4.3' puppeteer-coreUpload the Stagehand extension
Stagehand ships its extension inside the npm package. Export it as a ZIP file:
// export-extension.mjs
import { getExtensionArchive } from "@browserbasehq/stagehand";
import { writeFile } from "node:fs/promises";
await writeFile("stagehand-extension.zip", await getExtensionArchive());node export-extension.mjsThen upload
stagehand-extension.zipon the Extensions page of your account dashboard and name itstagehand. Wait until its status isactive. To upload from a script or CI pipeline instead, see Manage extensions from CI/CD.You only do this once per Stagehand version. Re-export and re-upload the extension whenever you upgrade
@browserbasehq/stagehand, so the extension matches the SDK.Connect and run your automation
The script below starts a session with the
stagehandextension loaded, gets a reconnect URL for it, and connects Stagehand to that URL. It then opens the Browserless site, clicks through to the docs withact(), and pulls the doc sections out withextract():import { localBrowser, Stagehand } from "@browserbasehq/stagehand";
import puppeteer from "puppeteer-core";
import { z } from "zod/v4";
const TOKEN = process.env.BROWSERLESS_API_KEY;
const BROWSERLESS_URL = "wss://production-sfo.browserless.io";
// Start a session with the Stagehand extension loaded, then get a URL that points back
// to that same browser. The extension dials this URL from inside Chrome, so it must not
// start a new session.
async function getReconnectUrl() {
const launch = JSON.stringify({ extensions: ["stagehand"] });
const browser = await puppeteer.connect({
browserWSEndpoint: `${BROWSERLESS_URL}?token=${TOKEN}&launch=${encodeURIComponent(launch)}`,
});
const [page] = await browser.pages();
const cdp = await page.createCDPSession();
const { browserWSEndpoint, error } = await cdp.send("Browserless.reconnect", {
timeout: 60000,
});
if (error) throw new Error(error);
await browser.disconnect();
return `${browserWSEndpoint}?token=${TOKEN}`;
}
// Use whichever LLM key is set. Stagehand also accepts google/, groq/, and cerebras/ models.
function getModel() {
if (process.env.ANTHROPIC_API_KEY) {
return { modelName: "anthropic/claude-sonnet-5-5", apiKey: process.env.ANTHROPIC_API_KEY };
}
if (process.env.OPENAI_API_KEY) {
return { modelName: "openai/gpt-5.4-mini", apiKey: process.env.OPENAI_API_KEY };
}
throw new Error("Set ANTHROPIC_API_KEY or OPENAI_API_KEY.");
}
// Pick the model before starting a session, so a missing key doesn't use one.
const model = getModel();
const reconnectUrl = await getReconnectUrl();
let browser;
try {
browser = await localBrowser.connect({
cdpUrl: reconnectUrl,
extensionCdpUrl: reconnectUrl,
});
} catch (error) {
// The session is waiting for a reconnect, so it stays open until the reconnect timeout.
// Reconnect once to close it. If that fails too, report the original error instead.
try {
const session = await puppeteer.connect({ browserWSEndpoint: reconnectUrl });
await session.close();
} catch {}
throw error;
}
// The Stagehand extension keeps its own connection to the session, so a run that throws
// before browser.close() leaves the session open until it times out.
try {
const stagehand = await Stagehand.create({ browser, model });
const [page] = await browser.context.pages();
await page.goto("https://www.browserless.io");
await stagehand.act("click on the Docs link in the navigation");
const { data } = await stagehand.extract(
"extract the main documentation categories or sections",
z.object({
sections: z.array(
z.object({
title: z.string(),
description: z.string(),
}),
),
}),
);
console.log(data.sections);
await stagehand.close();
} finally {
await browser.close();
}The
timeoutpassed toBrowserless.reconnectonly needs to cover the gap between disconnecting Puppeteer and connecting Stagehand. In v4,act(),observe(), andextract()return a result object, so read the extracted value fromdata.Response (trimmed; the exact sections depend on the live site):
[
{
title: 'Documentation',
description: 'Guides, references, and examples for Browserless'
},
{
title: 'QUICK REFERENCE',
description: 'Connection URLs and Endpoints; Launch Parameters'
},
{
title: 'INTEGRATIONS',
description: 'Browser Use; Stagehand; LangChain; Vercel AI SDK; ...'
},
...
]
Using Stagehand v3
Stagehand v3 drives the browser over CDP from your machine, so it connects to a regular Browserless URL with no extension or reconnect step. Install the v3 line:
npm install @browserbasehq/stagehand@3 zod
Then pass the Browserless WebSocket URL as cdpUrl:
import { Stagehand } from "@browserbasehq/stagehand";
const stagehand = new Stagehand({
env: "LOCAL",
localBrowserLaunchOptions: {
cdpUrl: `wss://production-sfo.browserless.io?token=${process.env.BROWSERLESS_API_KEY}`,
},
model: "anthropic/claude-sonnet-4-5",
});
await stagehand.init();
const page = stagehand.context.pages()[0];
await page.goto("https://www.browserless.io");
await stagehand.act("click on the Docs link in the navigation");
await stagehand.close();
v3 reads the provider key from the environment, so set ANTHROPIC_API_KEY for this example. v3's act() doesn't work with current OpenAI models, because OpenAI rejects its response schema; use v4 if you want OpenAI. v3 is still published under the v3-latest npm tag, but new Stagehand features ship in v4.
Why use Browserless with Stagehand
- No infrastructure: skip managing Chrome installations, updates, or scaling
- Global regions: choose US West (San Francisco), Europe (London), or Europe (Amsterdam) endpoints for lower latency
- Parallel sessions: run multiple Stagehand sessions at the same time
- Stealth and proxies: use stealth mode and residential proxies on sites that block automated browsers
Resources
- Stagehand documentation
- Browserless browser extensions
- Browserless connection options
- Standard sessions and
Browserless.reconnect - Stagehand GitHub repository
FAQ & Troubleshooting
localBrowser.connect() fails with File path cannot be resolved
Stagehand couldn't find its extension in the browser, so it tried to load it from a folder on your machine, which the remote browser can't read. Upload the extension as shown in step 3 and include launch={"extensions":["stagehand"]} when you start the session.
act() or observe() fails with active target not registered
The extension is connected to a different browser from your script. This happens when you pass a regular Browserless URL as cdpUrl, because the extension's connection starts a new session. Use the URL returned by Browserless.reconnect and pass it as both cdpUrl and extensionCdpUrl, as shown in step 4.
Stagehand fails after I upgraded @browserbasehq/stagehand
The extension in your Browserless account no longer matches the SDK version. Re-run export-extension.mjs, then upload the new ZIP under the same stagehand name. Uploading the same name replaces the old extension; wait for its status to become active before starting new sessions.
extract() fails with Invalid schema for response_format
OpenAI's structured outputs require every field in the schema. Replace .optional() with a required field, or use .nullable() if the value can be missing.
Which LLM models can I use with Stagehand?
The sample's getModel() uses Claude when ANTHROPIC_API_KEY is set and GPT when OPENAI_API_KEY is set. To use another provider, return a model object with a provider/model name and that provider's API key, such as { modelName: "google/gemini-3.5-flash", apiKey: process.env.GOOGLE_API_KEY }. Stagehand v4 supports openai/, anthropic/, google/, groq/, and cerebras/ model names.