Skip to main content
This guide helps you migrate from the legacy Stagehand Python SDK to the new Stainless-based SDK with a Bring Your Own Browser (BYOB) architecture.
The new Python SDK is a pure API client. You manage the browser yourself using Playwright, Selenium, Puppeteer, or any other browser automation tool. The SDK handles only the AI-powered operations.

Overview of Changes

BYOB Architecture

You bring your own browser driver (Playwright, Selenium, etc.). The SDK is now a pure API client that handles AI-powered operations.

Session-Based API

All operations require an explicit session_id. Start a session, perform operations, and end it when done.

Multi-Browser Control

Scale browsers easily and control multiple browsers at once by passing the session ID for each browser you want to control.

Simplified Client

Cleaner initialization with dedicated parameters for API keys and configuration.

Current Limitations

The new SDK does not yet support:
  • Custom Python LLM client classes (e.g., model_client_options)
  • However, we do support custom endpoints like Bedrock or LLM proxies as long as they are OpenAI-API compatible

Step-by-Step Migration

1. Update Imports

import asyncio
import logging
from stagehand import Stagehand, StagehandConfig, configure_logging

# Configure logging
configure_logging(
    level=logging.INFO,
    remove_logger_name=True,
    quiet_dependencies=True,
)

2. Client Initialization

config = StagehandConfig(
    env="BROWSERBASE",
    api_key=os.getenv("BROWSERBASE_API_KEY"),
    project_id=os.getenv("BROWSERBASE_PROJECT_ID"),
    headless=False,
    dom_settle_timeout_ms=3000,
    model_name="google/gemini-2.0-flash",
    self_heal=True,
    wait_for_captcha_solves=True,
    system_prompt="You are a browser automation assistant...",
    model_client_options={"apiKey": os.getenv("MODEL_API_KEY")},
    verbose=2,
)

stagehand = Stagehand(config)
await stagehand.init()
page = stagehand.page
Key differences:
  • Configuration options like dom_settle_timeout_ms, self_heal, system_prompt, and verbose are not available in the new SDK
  • model_name is specified when starting a session, not in the config
  • You must connect Playwright separately to interact with the page

3. Navigation

await page.goto("https://google.com/")

4. Direct Page Interactions (Playwright)

Any direct page manipulation should use Playwright’s native API.
# Click using Playwright locator (this was already Playwright)
await page.get_by_role("link", name="About", exact=True).click()

# Keyboard input
await page.keyboard.press("Enter")
In the old SDK, page was a Stagehand-enhanced Playwright page. In the new SDK, page is a standard Playwright page. Direct Playwright methods work the same way.

5. AI-Powered Actions (act)

await page.act("search for openai")

Acting on an Observed Element

observed = await page.observe("find all articles")
if observed:
    await page.act(observed[0])

6. Observing Elements (observe)

observed = await page.observe("find all articles")
if len(observed) > 0:
    element = observed[0]
    print(f"Found element: {element}")

7. Extracting Data (extract)

data = await page.extract("extract the first result from the search")
print(data.model_dump_json())
Key difference: The new SDK requires an explicit JSON schema for extraction. This provides better type safety and clearer expectations for the AI model.

8. Closing the Session

await stagehand.close()
Important: Always clean up both Playwright and the Stagehand session. Use a try/finally block to ensure cleanup happens even on errors.

9. Async vs Sync

async def main():
    stagehand = Stagehand(config)
    await stagehand.init()
    await page.goto("https://example.com")
    await stagehand.close()

asyncio.run(main())

Complete Migration Example

import asyncio
import logging
import os

from dotenv import load_dotenv
from stagehand import Stagehand, StagehandConfig, configure_logging

configure_logging(level=logging.INFO, remove_logger_name=True, quiet_dependencies=True)
load_dotenv()

async def main():
    config = StagehandConfig(
        env="BROWSERBASE",
        api_key=os.getenv("BROWSERBASE_API_KEY"),
        project_id=os.getenv("BROWSERBASE_PROJECT_ID"),
        headless=False,
        model_name="google/gemini-2.0-flash",
        model_client_options={"apiKey": os.getenv("MODEL_API_KEY")},
        verbose=2,
    )

    stagehand = Stagehand(config)
    await stagehand.init()
    page = stagehand.page

    print(f"Session: {stagehand.session_id}")

    # Navigate
    await page.goto("https://google.com/")

    # Direct Playwright interaction
    await page.get_by_role("link", name="About", exact=True).click()

    # AI-powered action
    await page.goto("https://google.com/")
    await page.act("search for openai")
    await page.keyboard.press("Enter")

    # Observe and act
    observed = await page.observe("find all articles")
    if observed:
        await page.act(observed[0])

    # Extract data
    data = await page.extract("extract the first result")
    print(data.model_dump_json())

    await stagehand.close()

if __name__ == "__main__":
    asyncio.run(main())

Quick Reference: Method Mapping

Old SDKNew SDK
Stagehand(config)Stagehand(browserbase_api_key=..., ...)
await stagehand.init()client.sessions.start(...)
stagehand.pageConnect Playwright separately
stagehand.session_idstart_response.data.session_id
await page.goto(url)page.goto(url) (Playwright)
await page.act(instruction)client.sessions.act(id=session_id, input=instruction, ...)
await page.observe(instruction)client.sessions.observe(id=session_id, instruction=..., ...)
await page.extract(instruction)client.sessions.extract(id=session_id, instruction=..., schema=..., ...)
await stagehand.close()browser.close() + client.sessions.end(id=session_id, ...)
configure_logging(...)Use standard logging module

Troubleshooting

Ensure you’re using the correct session_id returned from client.sessions.start().
Make sure your Browserbase API key has the correct permissions and the session is still active.
These are required for all session operations. Use x_language="python" and x_sdk_version="3.0.6" (or the latest version).
The new SDK requires an explicit JSON schema. Make sure your schema matches the expected output structure.

Need Help?