stagehand is a free, open source data extraction & web scraping project written in TypeScript and released under MIT. It has 25,910 GitHub stars, 1,805 forks and 376 open issues, and was last pushed 12 hours ago. On this registry it ranks #15 of 150 tracked projects in Data Extraction & Web Scraping, and is listed as an open source replacement for 1 paid product, with 5 head-to-head comparisons available. It gained 418 stars over the last 30 tracked days.

What is stagehand?

Stagehand is the MIT-licensed SDK for extracting data and interacting with any site on the web, available in TypeScript, Python, and Go, and aimed at developers building AI agents that need to drive a browser the way a person does rather than at engineers writing test suites.

What it is

Stagehand is a browser automation SDK that puts a model behind three verbs — observe(), act(), and extract() — so an agent can locate elements, work through a flow, and return structured data from the page. It ships as the @browserbasehq/stagehand package for TypeScript, a Python package, and a Go module at github.com/browserbase/stagehand/packages/sdk-go/v4. It runs against a browser started with localBrowser.launch({ userDataDir: "./browser-data" }) or against a cloud browser, and it drives headless Chrome over the Chrome DevTools Protocol (cdp). Model configuration is explicit, for example modelName: "openai/gpt-5.4-mini" with an API key passed through the environment.

The concrete problem it solves is the brittleness of selector-based automation. Stagehand states plainly that Playwright was built for testing while Stagehand was built for agents, which places it in the Playwright and headless Chrome ecosystem as a replacement for hand-written scripts that hard-code CSS and XPath selectors. act() self-heals when a site redesigns its form, so a login or billing flow does not have to be rewritten after every front-end change. observe() returns real selectors rather than generated clicks, which means credentials can be filled by Playwright locators and never reach the model. extract() returns schema-validated data instead of loose text, and cookies persisted to ./browser-data mean the next run starts already signed in.

Key capabilities

  • observe() returns real selectors for elements such as the email and password inputs, keeping credentials out of the model prompt.
  • act() self-heals when a site redesigns its form, so recorded flows survive front-end changes.
  • extract() accepts a schema — zod v4 in TypeScript, Pydantic BaseModel in Python — and returns schema-validated data.
  • Sessions persist: localBrowser.launch({ userDataDir: "./browser-data" }) keeps cookies so the next run starts signed in.
  • One API across three languages: TypeScript, Python, and Go.
  • Model choice is configurable through ModelConfig, including modelName and apiKey.
  • Runs against local headless Chrome or a cloud browser over the Chrome DevTools Protocol.
  • Adapters for Claude Code, Codex, Eve, Mastra, and more.

Who uses it and how

  • Teams automating logged-in workflows, such as opening a billing page and pulling every invoice from a table.
  • Agent builders wiring Stagehand behind Claude Code, Codex, Eve, or Mastra as the browser layer.
  • Data extraction pipelines that need typed output rather than scraped strings, using zod or Pydantic schemas.
  • API and platform teams working in Go, Python, or TypeScript that want one automation surface across languages.
  • Operators running headless Chrome locally during development and a cloud browser in production.

Getting started

Install the @browserbasehq/stagehand package for TypeScript, the Python package, or the Go module at github.com/browserbase/stagehand/packages/sdk-go/v4. The project points to its Docs and Quickstart at stagehand.dev for setup.

How it compares

Within this registry the closest named relative is Playwright, and the project draws the line itself: Playwright targets testing, while Stagehand targets agents. Stagehand sits on top of that same browser automation stack and adds an AI layer for element discovery, self-healing actions, and schema-validated extraction rather than replacing the underlying driver.

When to use it — and when not to

A self-hoster must operate a browser binary, a persistent user data directory such as ./browser-data, and an API key for a hosted model, which means per-run model cost and an external network dependency. Teams that want deterministic, zero-model automation costs, or that only need a conventional test suite, should stay with plain Playwright. The repository shows 376 open issues and the README excerpt covers the API but not deployment, scaling, or upgrade guidance, so anyone evaluating it for production should read the docs before committing.

project readme (upstream, from github) — read inline

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 · Quickstart · ⭐ Star this repo

Ask DeepWiki

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/v4@v4.0.0

Local runs need Chrome installed. Full setup: 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 and get 2x faster execution than Playwright cloud equivalent browsers. Configure the Model Gateway so you never wire up a provider, and enable server-side 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
}

Stealth mode, residential proxies, persistent contexts, and session recordings come with it. Get an API key · Browser configuration

Give your coding agent a browser

The hosted Browserbase MCP server 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" }
    }
  }
}

MCP setup · Available tools

Search and fetch without a browser

Fetch lets you grab the content of any URL as markdown. 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);

Search · Fetch

Docs and resources

Quickstart Empty directory to working automation in three steps
act · extract · observe The three primitives
Migrate from Playwright Port an existing suite
Integrations CrewAI, Mastra, Deep Agents, Vercel AI SDK, Claude Code, Codex
Python SDK · Go SDK Language-specific guides
Ask DeepWiki Ask questions about this codebase

Join the community

Stagehand is built in the open, and the fastest way to shape it is to show up.

Contributing

We're focused on improving reliability, extensibility, speed, and cost, in that order. Bug fixes and small improvements are the best way to get started. For anything larger, reach out to Miguel Gonzalez or Paul Klein on Discord first so we can make sure it lands.

Stagehand is a TypeScript, Python, and Go monorepo driven by just:

git clone https://github.com/browserbase/stagehand.git
cd stagehand
just install
just generate
just build

export OPENAI_API_KEY="your-openai-api-key"
just example act # runs packages/sdk-ts/examples/act.ts

See CONTRIBUTING.md for the full TypeScript, Python, and Go setup.

Acknowledgements

We'd like to thank the following people for their major contributions to Stagehand:

License

Licensed under the MIT License.

Copyright 2026 Browserbase, Inc.

"Stagehand" is a trademark of Browserbase, Inc.

Hosted alternatives to stagehand

stagehand is self-hostable. These are the managed services that do the same job — no infrastructure to run, metered by usage. Listed with the vendor's own pricing.

Frequently asked questions

Is stagehand free to use?

stagehand is open source under the MIT licence. There is no licence fee and no seat count — you can self-host it or, where the project offers one, pay a vendor for a managed version instead.

What does stagehand do?

The SDK to extract data and interact with any site on the web. Get started with Claude Code, Codex, Eve, Mastra, and more.

What is stagehand written in?

stagehand is primarily written in TypeScript. Its source is publicly available at https://github.com/browserbase/stagehand, and it has 25,910 GitHub stars.

What is a good open source alternative to Browserbase?

stagehand is one of the open source options listed as an alternative to Browserbase. Compare licences, stars and activity side by side on the stagehand profile.