MCPcopy Index your code
hub / github.com/browserbase/stagehand-python

github.com/browserbase/stagehand-python @v3.21.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release v3.21.0 ↗ · + Follow
1,319 symbols 5,504 edges 93 files 173 documented · 13%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

The AI Browser Automation Framework

Read the Docs

  <img alt="MIT License" src="https://raw.githubusercontent.com/browserbase/stagehand/main/media/light_license.svg" />

  <img alt="Discord Community" src="https://raw.githubusercontent.com/browserbase/stagehand/main/media/light_discord.svg" />

PyPI version

<a href="https://trendshift.io/repositories/12122" target="_blank"><img src="https://trendshift.io/api/badge/repositories/12122" alt="browserbase%2Fstagehand | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>

If you're looking for other languages, you can find them here

Vibe code Stagehand with Director

<img alt="Director" src="https://raw.githubusercontent.com/browserbase/stagehand/main/media/director_icon.svg" width="25" />

[!TIP] Migrating from the old v2 Python SDK? See our migration guide here.

What is Stagehand?

Stagehand is a browser automation framework used to control web browsers with natural language and code. By combining the power of AI with the precision of code, Stagehand makes web automation flexible, maintainable, and actually reliable.

Why Stagehand?

Most existing browser automation tools either require you to write low-level code in a framework like Selenium, Playwright, or Puppeteer, or use high-level agents that can be unpredictable in production. By letting developers choose what to write in code vs. natural language (and bridging the gap between the two) Stagehand is the natural choice for browser automations in production.

  1. Choose when to write code vs. natural language: use AI when you want to navigate unfamiliar pages, and use code when you know exactly what you want to do.

  2. Go from AI-driven to repeatable workflows: Stagehand lets you preview AI actions before running them, and also helps you easily cache repeatable actions to save time and tokens.

  3. Write once, run forever: Stagehand's auto-caching combined with self-healing remembers previous actions, runs without LLM inference, and knows when to involve AI whenever the website changes and your automation breaks.

Installation

# install from PyPI
uv pip install stagehand

For local development or when working from this repository, sync the dependency lockfile with uv (see the Local development section below) before running project scripts.

Requirements

Python 3.9 or higher.

Running the Example

Set your environment variables (from examples/.env.example):

  • MODEL_API_KEY
  • BROWSERBASE_API_KEY
cp examples/.env.example examples/.env
# Edit examples/.env with your credentials.

The examples load examples/.env automatically.

Examples and dependencies:

  • examples/full_example.py: stagehand only
  • examples/act_example.py: stagehand only
  • examples/agent_execute.py: stagehand only
  • examples/local_example.py: stagehand only
  • examples/logging_example.py: stagehand only
  • examples/remote_browser_playwright_example.py: Playwright + Playwright browsers
  • examples/local_browser_playwright_example.py: Playwright + Playwright browsers
  • examples/playwright_page_example.py: Playwright + Playwright browsers
  • examples/byob_example.py: Playwright + Playwright browsers
  • examples/pydoll_tab_example.py: pydoll-python (Python 3.10+)

Multiregion support: see examples/local_server_multiregion_browser_example.py.

Run any example:

uv run python examples/remote_browser_playwright_example.py

Usage

This mirrors examples/remote_browser_playwright_example.py.

import os

from playwright.sync_api import sync_playwright

from env import load_example_env
from stagehand import Stagehand


def main() -> None:
    load_example_env()

    with Stagehand(
        server="remote",
        browserbase_api_key=os.environ.get("BROWSERBASE_API_KEY"),
        model_api_key=os.environ.get("MODEL_API_KEY"),
    ) as client:
        session = client.sessions.start(
            model_name="anthropic/claude-sonnet-4-6",
            browser={"type": "browserbase"},
        )

        cdp_url = session.data.cdp_url
        if not cdp_url:
            raise RuntimeError("No cdp_url returned from the API for this session.")

        with sync_playwright() as p:
            browser = p.chromium.connect_over_cdp(cdp_url)
            context = browser.contexts[0] if browser.contexts else browser.new_context()
            page = context.pages[0] if context.pages else context.new_page()

            client.sessions.navigate(session.id, url="https://news.ycombinator.com")
            page.wait_for_load_state("domcontentloaded")

            observe_stream = client.sessions.observe(
                session.id,
                instruction="find the link to view comments for the top post",
                stream_response=True,
                x_stream_response="true",
            )
            for _ in observe_stream:
                pass

            act_stream = client.sessions.act(
                session.id,
                input="Click the comments link for the top post",
                stream_response=True,
                x_stream_response="true",
            )
            for _ in act_stream:
                pass

            extract_stream = client.sessions.extract(
                session.id,
                instruction="extract the text of the top comment on this page",
                schema={
                    "type": "object",
                    "properties": {
                        "commentText": {"type": "string"},
                        "author": {"type": "string"},
                    },
                    "required": ["commentText"],
                },
                stream_response=True,
                x_stream_response="true",
            )
            for _ in extract_stream:
                pass

            execute_stream = client.sessions.execute(
                session.id,
                execute_options={
                    "instruction": "Click the 'Learn more' link if available",
                    "max_steps": 3,
                },
                agent_config={
                    "model": {"model_name": "anthropic/claude-opus-4-6"},
                    "cua": False,
                },
                stream_response=True,
                x_stream_response="true",
            )
            for _ in execute_stream:
                pass

        client.sessions.end(session.id)


if __name__ == "__main__":
    main()

Client configuration

Configure the client using environment variables:

from stagehand import AsyncStagehand

client = AsyncStagehand()

Or manually:

from stagehand import AsyncStagehand

client = AsyncStagehand(
    browserbase_api_key="My Browserbase API Key",
    model_api_key="My Model API Key",
)

Or using a combination of the two approaches:

from stagehand import AsyncStagehand

client = AsyncStagehand(
    # Configures using environment variables
    browserbase_api_key="My Browserbase API Key",  # override just this one
)

See this table for the available options:

Keyword argument Environment variable Required Default value
browserbase_api_key BROWSERBASE_API_KEY true -
browserbase_project_id - false -
model_api_key MODEL_API_KEY true -
base_url STAGEHAND_API_URL false "https://api.stagehand.browserbase.com"

browserbase_project_id is deprecated, accepted for backwards compatibility, and ignored. STAGEHAND_BASE_URL remains supported as a deprecated fallback when STAGEHAND_API_URL is unset.

Keyword arguments take precedence over environment variables.

[!TIP] Don't create more than one client in the same application. Each client has a connection pool, which is more efficient to share between requests.

Modifying configuration

To temporarily use a modified client configuration while reusing the same connection pool, call with_options() on any client:

client_with_options = client.with_options(model_api_key="sk-your-llm-api-key-here", max_retries=42)

The with_options() method does not affect the original client.

Requests and responses

To send a request to the Stagehand API, call the corresponding client method using keyword arguments.

Nested request parameters are dictionaries typed using TypedDict. Responses are Pydantic models which also provide helper methods like:

  • Serializing back into JSON: model.to_json()
  • Converting to a dictionary: model.to_dict()

Immutability

Response objects are Pydantic models. If you want to build a modified copy, prefer model.model_copy(update={...}) (Pydantic v2) rather than mutating in place.

Asynchronous execution

This SDK recommends using AsyncStagehand and awaiting each API call:

import asyncio
from stagehand import AsyncStagehand


async def main() -> None:
    client = AsyncStagehand()
    session = await client.sessions.start(model_name="anthropic/claude-sonnet-4-6")
    response = await session.act(input="click the first link on the page")
    print(response.data)


asyncio.run(main())

With aiohttp

By default, the async client uses httpx for HTTP requests. For improved concurrency performance you may also use aiohttp as the HTTP backend.

Install aiohttp:

# install from PyPI
uv pip install stagehand[aiohttp]

Then instantiate the client with http_client=DefaultAioHttpClient():

import asyncio
from stagehand import AsyncStagehand, DefaultAioHttpClient


async def main() -> None:
    async with AsyncStagehand(http_client=DefaultAioHttpClient()) as client:
        session = await client.sessions.start(model_name="anthropic/claude-sonnet-4-6")
        response = await session.act(input="click the first link on the page")
        print(response.data)


asyncio.run(main())

Streaming responses

We provide support for streaming responses using Server-Sent Events (SSE).

To enable SSE streaming, you must:

  1. Ask the server to stream by setting x_stream_response="true" (header), and
  2. Tell the client to parse an SSE stream by setting stream_response=True.
import asyncio

from stagehand import AsyncStagehand


async def main() -> None:
    async with AsyncStagehand() as client:
        session = await client.sessions.start(model_name="anthropic/claude-sonnet-4-6")

        stream = await client.sessions.act(
            id=session.id,
            input="click the first link on the page",
            stream_response=True,
            x_stream_response="true",
        )
        async for event in stream:
            # event is a StreamEvent (type: "system" | "log")
            print(event.type, event.data)


asyncio.run(main())

Raw responses

The SDK defines methods that deserialize responses into Pydantic models. However, these methods don't provide access to response headers, status code, or the raw response body.

To access this data, prefix any HTTP method call on a client or service with with_raw_response:

import asyncio

from stagehand import AsyncStagehand


async def main() -> None:
    async with AsyncStagehand() as client:
        response = await client.sessions.with_raw_response.start(model_name="anthropic/claude-sonnet-4-6")
        print(response.headers.get("X-My-Header"))

        session = response.parse()  # get the object that `sessions.start()` would have returned
        print(session.data)


asyncio.run(main())

.with_streaming_response

The with_raw_response interface eagerly reads the full response body when you make the request.

To stream the response body (not SSE), use `with_streaming_respons

Core symbols most depended-on inside this repo

get
called by 206
src/stagehand/_types.py
construct
called by 99
src/stagehand/_models.py
post
called by 65
src/stagehand/_base_client.py
assert_matches_type
called by 65
tests/utils.py
_build_request
called by 63
src/stagehand/_base_client.py
start
called by 51
src/stagehand/resources/sessions.py
parse
called by 48
src/stagehand/_qs.py
extract
called by 37
src/stagehand/_custom/session.py

Shape

Method 577
Function 443
Class 289
Route 10

Languages

Python100%

Modules by API surface

tests/test_client.py132 symbols
tests/api_resources/test_sessions.py120 symbols
src/stagehand/_base_client.py113 symbols
tests/test_models.py77 symbols
src/stagehand/_response.py60 symbols
tests/test_transform.py59 symbols
src/stagehand/_custom/session.py44 symbols
src/stagehand/_client.py42 symbols
src/stagehand/_streaming.py38 symbols
src/stagehand/_models.py38 symbols
src/stagehand/_utils/_utils.py33 symbols
src/stagehand/resources/sessions.py31 symbols

For agents

$ claude mcp add stagehand-python \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact