Retrieved article excerpt
Open article · Retrieved 2026-09-18T17:24:00.381580+00:00
[Stagehand](https://stagehand.dev)
**Stagehand is the SDK to extract data and interact with any site on the web.**
Playwright was built for testing. Stagehand is built for agents, in TypeScript, Python, and Go.
[**Docs**](https://docs.stagehand.dev) ·
[**Quickstart**](https://docs.stagehand.dev/v4/first-steps/quickstart) ·
[**⭐ Star this repo**](https://github.com/browserbase/stagehand/stargazers)
[MIT License](https://github.com/browserbase/stagehand/tree/main?tab=MIT-1-ov-file#MIT-1-ov-file)
[Discord Community](https://discord.gg/stagehand)
[Ask DeepWiki](https://deepwiki.com/browserbase/stagehand)
## AI that uses the browser like humans.
Sign in once, keep the session, and pull structured data out the other side.
```
import { localBrowser, Stagehand } from "@browserbasehq/stagehand";
import { z } from "zod/v4";
// Cookies persist in ./browser-data, so the next run starts already signed in
const browser = await localBrowser.launch({ userDataDir: "./browser-data" });
const stagehand = await Stagehand.create({
browser,
model: { modelName: "openai/gpt-5.4-mini", apiKey: process.env.OPENAI_API_KEY },
});
const [page] = await browser.context.pages();
await page.goto("https://app.example.com/login");
// observe() returns real selectors, so credentials never reach the model
const { data: email } = await stagehand.observe("find the email input");
const { data: password } = await stagehand.observe("find the password input");
await page.locator(email[0].selector).fill(process.env.APP_EMAIL!);
await page.locator(password[0].selector).fill(process.env.APP_PASSWORD!);
// act() self-heals when the site redesigns its form
await stagehand.act("click the sign in button");
await stagehand.act("open the billing page");
// extract() returns schema-validated data
const { data } = await stagehand.extract(
"extract every invoice in the table",
z.object({
invoices: z.array(z.object({ number: z.string(), amount: z.number(), paid: z.boolean() })),
}),
);
console.log(data.invoices);
await stagehand.close();
await browser.close();
```
**Python**
```
import asyncio
import os
from pydantic import BaseModel
from stagehand import Stagehand, local_browser
class Invoice(BaseModel):
number: str
amount: float
paid: bool
class Invoices(BaseModel):
invoices: list[Invoice]
async def main() -> None:
# Cookies persist in ./browser-data, so the next run starts already signed in
browser = await local_browser.launch(user_data_dir="./browser-data")
try:
stagehand = await Stagehand.create(
browser=browser,
model="openai/gpt-5.4-mini",
model_api_key=os.environ["OPENAI_API_KEY"],
)
try:
page = (await browser.context.pages())[0]
await page.goto("https://app.example.com/login")
# observe() returns real selectors, so credentials never reach the model
email = await stagehand.observe("find the email input")
password = await stagehand.observe("find the password input")
await page.locator(email.data[0].selector).fill(os.environ["APP_EMAIL"])
await page.locator(password.data[0].selector).fill(os.environ["APP_PASSWORD"])
# act() self-heals when the site redesigns its form
await stagehand.act("click the sign in button")
await stagehand.act("open the billing page")
# extract() returns schema-validated data
result = await stagehand.extract(
"extract every invoice in the table",
Invoices,
)
print(result.data.invoices)
finally:
await stagehand.close()
finally:
await browser.close()
asyncio.run(main())
```
**Go**
```
package main
import (
"context"
"errors"
"fmt"
"log"
"os"
stagehand "github.com/browserbase/stagehand/packages/sdk-go/v4"
)
type invoice struct {
Number string `json:"number"`
Amount float64 `json:"amount"`
Paid bool `json:"paid"`
}
type invoices struct {
Invoices []invoice `json:"invoices"`
}
func main() {
if err := run(context.Background()); err != nil {
log.Fatal(err)
}
}
func run(ctx context.Context) (err error) {
// Cookies persist in ./browser-data, so the next run starts already signed in
browser, err := stagehand.LaunchLocalBrowser(ctx, &stagehand.LocalBrowserLaunchOptions{
UserDataDir: "./browser-data",
})
if err != nil {
return err
}
defer func() { err = errors.Join(err, browser.Close(ctx)) }()
modelAPIKey := os.Getenv("OPENAI_API_KEY")
client, err := stagehand.Create(ctx, stagehand.CreateOptions{
Browser: browser,
Model: &stagehand.ModelConfig{
ModelName: "openai/gpt-5.4-mini",
APIKey: &modelAPIKey,
},
})
if err != nil {
return err
}
defer func() { err = errors.Join(err, client.Close(ctx)) }()
browserContext, err := browser.Context()
if err != nil {
return err
}
pages, err := browserContext.Pages(ctx)
if err != nil {
return err
}
page := pages[0]
if _, err := page.Goto(ctx, "https://app.example.com/login", nil); err != nil {
return err
}
// Observe returns real selectors, so credentials never reach the model
emailInstruction := "find the email input"
email, err := client.Observe(ctx, &emailInstruction, nil)
if err != nil {
return err
}
if err := page.Locator(email.Data[0].Selector).Fill(ctx, os.Getenv("APP_EMAIL")); err != nil {
return err
}
passwordInstruction := "find the password input"
password, err := client.Observe(ctx, &passwordInstruction, nil)
if err != nil {
return err
}
if err := page.Locator(password.Data[0].Selector).Fill(ctx, os.Getenv("APP_PASSWORD")); err != nil {
return err
}
// Act self-heals when the site redesigns its form
if _, err := client.Act(ctx, stagehand.ActInstruction("click the sign in button"), nil); err != nil {
return err
}
if _, err := client.Act(ctx, stagehand.ActInstruction("open the billing page"), nil); err != nil {
return err
}
// Extract returns data decoded into a Go type
extracted, err := stagehand.Extract[invoices](
ctx,
client,
"extract every invoice in the table",
nil,
)
if err != nil {
return err
}
fmt.Println(extracted.Data.Invoices)
return nil
}
```
## Install
```
pnpm add @browserbasehq/stagehand 'zod@~4.4.3'
```
**Python**
```
pip install stagehand
```
**Go**
```
go get github.com/browserbase/stagehand/packages/sdk-go/[email protected]
```
Local runs need [Chrome](https://www.google.com/chrome/) installed. Full setup: [Quickstart](https://docs.stagehand.dev/v4/first-steps/quickstart).
## Why Stagehand
| | |
| --- | --- |
| **Familiar APIs** | The Playwright-style methods you and your agents already know: `goto`, `click`, `locator`, `screenshot`. |
| **Token efficiency** | Hybrid accessibility-tree trimming gives agents exactly the page context they need and nothing more. |
| **Faster in production** | Stagehand runs as an extension next to the browser, cutting round-trip latency on every action. |
| **Self-healing** | `act`, `observe`, and `extract` refresh how an action happens when the site changes underneath it. |
| **Built for agents** | WebMCP, clipboard support, batch commands, deep locators for nested iframes and closed Shadow DOMs, OTel traces. |
| **Three languages** | One complete browser driver across TypeScript, Python, and Go. |
## Run it on Browserbase
Point the same script at [Browserbase](https://www.browserbase.com) and get 2x faster execution than Playwright cloud equivalent browsers. Configure the [Model Gateway](https://docs.stagehand.dev/v4/configuration/models#model-gateway) so you never wire up a provider, and enable [server-side caching](https://docs.stagehand.dev/v4/best-practices/caching) to cache repeated actions.
```
import { browserbase, Stagehand } from "@browserbasehq/stagehand";
const browser = await browserbase.launch({ apiKey: process.env.BROWSERBASE_API_KEY! });
// No model configuration: the Model Gateway picks the cheapest model for each action
// cache: true: identical calls come back from Browserbase, no tokens spent
const stagehand = await Stagehand.create({ browser, cache: true });
```
**Python**
```
import os
from stagehand import Stagehand, browserbase
browser = await browserbase.launch(api_key=os.environ["BROWSERBASE_API_KEY"])
# No model configuration: the Model Gateway picks the cheapest model for each action
# cache=True: identical calls come back from Browserbase, no tokens spent
stagehand = await Stagehand.create(browser=browser, cache=True)
```
**Go**
```
browser, err := stagehand.LaunchBrowserbase(ctx, stagehand.BrowserbaseLaunchOptions{
APIKey: os.Getenv("BROWSERBASE_API_KEY"),
})
if err != nil {
return err
}
// No model configuration: the Model Gateway picks the cheapest model for each action
// CacheEnabled(true): identical calls come back from Browserbase, no tokens spent
cache := stagehand.CacheEnabled(true)
client, err := stagehand.Create(ctx, stagehand.CreateOptions{
Browser: browser,
Cache: &cache,
})
if err != nil {
return err
}
```
[Verified mode](https://docs.browserbase.com/platform/identity/verified-customization), [residential proxies](https://docs.browserbase.com/platform/identity/proxies), [persistent contexts](https://docs.browserbase.com/platform/browser/core-features/contexts), and [session recordings](https://docs.browserbase.com/platform/browser/observability/session-replay) come with it. [Get an API key](https://www.browserbase.com/overview) and learn how to configure your browser [here](https://docs.stagehand.dev/v4/configuration/browser).
## Give your coding agent a browser
The hosted Browserbase [MCP server](https://docs.stagehand.dev/integrations/mcp/setup) puts `navigate`, `act`, `observe`, and `extract` in any MCP client — no install, no local browser.
```
claude mcp add --transport http browserbase https://mcp.browserbase.com/mcp \
--header "Authorization: Bearer $BROWSERBASE_API_KEY"
```
**Cursor, Codex, and other MCP clients**
```
{
"mcpServers": {
"browserbase": {
"url": "https://mcp.browserbase.com/mcp",
"headers": { "Authorization": "Bearer YOUR_BROWSERBASE_API_KEY" }
}
}
}
```
## Search and fetch without a browser
[Fetch](https://docs.stagehand.dev/v4/add-ons/fetch) lets you grab the content of any URL as markdown. [Search](https://docs.stagehand.dev/v4/add-ons/search) provides fast, token-efficient web search results.
Both as a lightweight complement to browser sessions.
```
import { browserbase } from "@browserbasehq/stagehand";
const { results } = await browserbase.search({
apiKey: process.env.BROWSERBASE_API_KEY!,
query: "browser agent frameworks",
numResults: 5,
});
const fetched = await browserbase.fetch({
apiKey: process.env.BROWSERBASE_API_KEY!,
url: results[0].url,
format: "markdown",
});
console.log(fetched.content);
```
## Docs and resources
| | |
| --- | --- |
| [Quickstart](https://docs.stagehand.dev/v4/first-steps/quickstart) | Empty directory to working automation in three steps |
| [page](https://docs.stagehand.dev/v4/reference/page) · [locator](https://docs.stagehand.dev/v4/reference/locator) | Playwright-style browser and element APIs |
| [WebMCP](https://docs.stagehand.dev/v4/basics/webmcp) | Discover and invoke WebMCP tools exposed by web pages |
| [act](https://docs.stagehand.dev/v4/basics/act) · [extract](https://docs.stagehand.dev/v4/basics/extract) · [observe](https://docs.stagehand.dev/v4/basics/observe) | Browser actions and data extraction with natural language |
| [Search](https://docs.stagehand.dev/v4/add-ons/search) · [Fetch](https://docs.stagehand.dev/v4/add-ons/fetch) | Web search and page content without a browser |
| [Migrate from Playwright](https://docs.stagehand.dev/v4/migrations/playwright) | Port an existing suite |
| [Integrations](https://docs.stagehand.dev/v4/integrations/overview) | CrewAI, Mastra, Deep Agents, Vercel AI SDK, Claude Code, Codex |
| [Python SDK](https://