Loading...
Loading...
Migrate browser-use (Python) browser-automation scripts to Stagehand v3 (TypeScript) on Browserbase. Use when the user wants to convert, port, rewrite, or migrate a browser-use Agent script to Stagehand, map browser-use features/APIs to Stagehand primitives (act/extract/observe/agent), or move agentic browser automation onto Browserbase with more determinism. Triggers on "browser-use", "browser_use", or "Agent(task=...)".
npx skill4agent add browserbase/skills browser-use-to-stagehand/browser-use-to-stagehandSource of truth & versions. This skill's durable value is the judgment — the determinism spectrum and the decompose-vs-agent decision — not the API specifics, which drift every release. The code mappings here are a snapshot validated against3.6.x and browser-use 0.13.x (2026-06). On any conflict, the live docs win — always verify against the installed package and these sources before emitting code:@browserbasehq/stagehand
- Stagehand v3: https://docs.stagehand.dev/v3 · installed types:
node_modules/@browserbasehq/stagehand- Browserbase: https://docs.browserbase.com
- browser-use: https://docs.browser-use.com
If the installed Stagehand major is not 3, treat this skill as conceptual only and follow the live docs for every signature.
references/api-mapping.mdreferences/determinism.mdagent()actextractobserveobserveactAgent(task=…)references/trace-assisted.mdreferences/guide.mdreferences/prompt.mdEXAMPLES.mdFirst, gate on scope — is this even migratable? Not every browser-use file is anscript. If the source is browser-use running as an MCP server (Agent(task=…), auvx browser-use --mcpconfig) there is no Stagehand equivalent — flag it as out of scope, don't invent one (see api-mapping §3.7b). If the browser-use call is embedded in a larger app (a class/tool wrapper, web route, queue task), convert only the browser-use surface and preserve the surrounding app glue — see api-mapping §3.8.mcpServers
browser_use.betafrom browser_use import Agent, ChatBrowserUseChatBrowserUsebrowser_use.betatask=Chat*cdp_urluser_data_dirstorage_stateoutput_model_schemasensitive_dataallowed_domainsmax_steps@tools.actionControllerinitial_actionspage_extraction_llmplanner_llmpage.goto(url)act("…")observe()act(action)extract("…", zodSchema)stagehand.agent().execute(...)maxStepssystemPromptagent()agent()node_modules/@browserbasehq/stagehandimport { Stagehand } from "@browserbasehq/stagehand";import { z } from "zod";const page = stagehand.context.pages()[0];stagehand.act(...)stagehand.extract(...)stagehand.observe(...)page.act(...)"provider/model"env: "BROWSERBASE"env: "LOCAL"variablesprocess.envawait stagehand.init()await stagehand.close()finallyallowed_domainsagent(task=…)sessions.logs.listpackage.json{
"name": "stagehand-migration",
"type": "module",
"scripts": { "start": "tsx index.ts" },
"dependencies": {
"@browserbasehq/stagehand": "^3.0.0",
"dotenv": "^16.0.0",
"zod": "^3.25.0"
},
"devDependencies": { "tsx": "^4.0.0", "typescript": "^5.0.0" }
}Add(Vercel AI SDK) only if a custom browser-use action maps to an agent"ai": "^5.0.0". Pin v5, not v4 — Stagehand 3.6.x bundlestoolv5 and typesaias the v5agent({ tools }), where a tool's schema field isToolSet. The v4inputSchemahelper emitstool()instead and will fail to type-check against Stagehand's v5parameters. If you can't control the hoistedToolSetversion, skip theaihelper and pass a plain objecttool()— it satisfies the v5{ description, inputSchema: zodSchema, execute }regardless of whichToolSetmajor resolves.ai
.envBROWSERBASE_API_KEY=...
BROWSERBASE_PROJECT_ID=...
ANTHROPIC_API_KEY=... # or the provider matching your model stringindex.tsimport "dotenv/config";
import { Stagehand } from "@browserbasehq/stagehand";
import { z } from "zod";
async function main() {
const stagehand = new Stagehand({
env: "BROWSERBASE",
model: "anthropic/claude-sonnet-4-6",
});
await stagehand.init();
try {
const page = stagehand.context.pages()[0];
await page.goto("https://example.com"); // deterministic skeleton
await stagehand.act("…"); // AI where the page varies
const data = await stagehand.extract("…", z.object({ /* … */ })); // structured reads
console.log(data);
} finally {
await stagehand.close();
}
}
main().catch((err) => { console.error(err); process.exit(1); });stagehand.act/extract/observestagehand.context.pages()[0]"provider/model".envextractzodvariablesprocess.envinit()close()close()finallypage.act()stagehand.pagemodelNamemodelClientOptionsenableCachingact()page.gotoobserveactagent()allowed_domains