stagehand is a free, open source data extraction & web scraping project written in TypeScript and released under MIT. It has 24,322 GitHub stars, 1,685 forks and 376 open issues, and was last pushed 6 hours ago. On this registry it ranks #11 of 45 tracked projects in Data Extraction & Web Scraping, with 5 head-to-head comparisons available.

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/[email protected]

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 { brow

readme truncated — read the full docs on github

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 24,322 GitHub stars.