> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stagehand.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# AI rules

> Give your AI coding assistant the rules it needs to write correct Stagehand v4 code.

You're likely using AI to write code, and there's a **right and wrong way to do it.** This page collects the rules, configs, and copy-paste snippets that get your coding assistant writing correct Stagehand v4 code.

## Quickstart

<CardGroup cols={2}>
  <Card title="Add MCP servers" icon="screwdriver-wrench">
    Configure Context7, DeepWiki, and Stagehand Docs in your MCP client.
  </Card>

  <Card title="Pin editor rules" icon="memo">
    Drop in `cursorrules` and `claude.md` so AI agents/assistants always emit Stagehand patterns.
  </Card>
</CardGroup>

## Using MCP servers

MCP (Model Context Protocol) servers act as intermediaries that connect AI systems to external data sources and tools. These servers enable your coding assistant to access real-time information, execute tasks, and retrieve structured data to enhance code generation accuracy.

The following **MCP servers** provide specialized access to Stagehand documentation and related resources:

<Accordion title="Context7 by Upstash" icon="database">
  Provides semantic search across documentation and codebase context. Context7 enables AI assistants to find relevant code patterns, examples, and implementation details from your project history. It maintains contextual understanding of your development workflow and can surface related solutions from previous work.

  **Installation:**

  ```json theme={null}
  {
    "mcpServers": {
      "context7": {
        "command": "npx",
        "args": ["-y", "@upstash/context7-mcp"]
      }
    }
  }
  ```
</Accordion>

<Accordion title="DeepWiki by Cognition" icon="book-open">
  Indexes GitHub repositories and their documentation. DeepWiki gives coding assistants access to repository structure, API references, and code relationships across the Stagehand repository.

  **Installation:**

  ```json theme={null}
  {
    "mcpServers": {
      "deepwiki": {
        "url": "https://mcp.deepwiki.com/mcp"
      }
    }
  }
  ```
</Accordion>

<Accordion title="Stagehand docs by Mintlify" icon="mintbit">
  Direct access to official Stagehand documentation. This MCP server provides AI assistants with up-to-date API references, configuration options, and usage examples for accurate code generation. Mintlify auto-generates this server from the official docs, ensuring your AI assistant always has the latest information.

  **Usage:**

  ```json theme={null}
  {
    "mcpServers": {
      "stagehand-docs": {
        "url": "https://docs.stagehand.dev/mcp"
      }
    }
  }
  ```
</Accordion>

**How MCP servers enhance your development:**

* **Real-time Documentation Access:** AI assistants can query the latest Stagehand docs, examples, and best practices
* **Context-Aware Code Generation:** Servers provide relevant code patterns and configurations based on your specific use case
* **Reduced Integration Overhead:** Standardized protocol eliminates the need for custom integrations with each documentation source
* **Enhanced Accuracy:** AI agents receive structured, up-to-date information rather than relying on potentially outdated training data

<Tip>
  **Prompting tip:**
  Explicitly ask your coding agent/assistant to use these MCP servers to fetch relevant information from the docs so they have better context and know how to write proper Stagehand code.

  ie. **"Use the stagehand-docs MCP to fetch the act/observe guidelines, then generate code that follows them. Prefer cached observe results."**
</Tip>

## Editor rule files (copy-paste)

Drop these in `.cursorrules`, `windsurfrules`, `claude.md`, or any agent rule framework:

<Tabs>
  <Tab title="TypeScript">
    ````md theme={null}
    # Stagehand Project

    This is a project that uses Stagehand v4, a browser automation framework with AI-powered `act`, `extract`, and `observe` methods.

    The main class can be imported as `Stagehand` from `@browserbasehq/stagehand`.

    **Key Classes:**

    - `Stagehand`: Main orchestrator class providing `act`, `extract`, and `observe` methods
    - `browser.context`: A `BrowserContext` object that manages pages, cookies, and the clipboard
    - `page`: Individual page objects accessed via `browser.context.activePage()`, `browser.context.pages()`, or created with `browser.context.newPage()`

    There is no `agent` API in v4. Compose `observe`, `act`, and `extract` in your own control flow instead.

    ## Initialize

    ```typescript
    import { browserbase, localBrowser, Stagehand } from "@browserbasehq/stagehand";

    const browser = await localBrowser.launch({ headless: true });
    const stagehand = await Stagehand.create({
      browser,
      model: {
        modelName: "openai/gpt-5.4-mini",
        apiKey: process.env.OPENAI_API_KEY,
      },
      logging: { level: "info", format: "pretty" },
    });

    // Access the browser context and pages
    const [page] = await browser.context.pages();
    const context = browser.context;

    // Create new pages if needed
    const page2 = await browser.context.newPage();
    ```

    For Browserbase cloud browsers, pass the Browserbase API key to `browserbase.launch()`:

    ```typescript
    const browser = await browserbase.launch({
      apiKey: process.env.BROWSERBASE_API_KEY,
    });
    const stagehand = await Stagehand.create({
      browser,
      model: { modelName: "openai/gpt-5.4-mini", apiKey: process.env.OPENAI_API_KEY },
    });
    ```

    Stagehand never reads environment variables for you. Always pass keys explicitly.

    ## Act

    Actions are called on the `stagehand` instance (not the page). `act` takes either a string instruction or an `Action` from `observe`. Use atomic, specific instructions:

    ```typescript
    // Act on the current active page
    await stagehand.act("click the sign in button");

    // Act on a specific page (when you need to target a page that isn't currently active)
    await stagehand.act("click the sign in button", { page: page2 });
    ```

    **Important:** Act instructions should be atomic and specific:

    - Good: "Click the sign in button" or "Type 'hello' into the search input"
    - Bad: "Order me pizza" or "Type in the search bar and hit enter" (multi-step)

    Use `variables` for secrets. Values are substituted locally and never sent to the model:

    ```typescript
    await stagehand.act("type %password% into the password field", {
      variables: { password: process.env.USER_PASSWORD },
    });
    ```

    ### Observe Then Act Pattern (Recommended)

    `act` accepts either a string instruction or an `Action` returned by `observe`. Use `observe` to inspect the candidate action, then pass it back to `act` for deterministic replay with no inference:

    ```typescript
    const { data: actions } = await stagehand.observe("Click the sign in button");
    const [action] = actions;

    if (action?.method === "click") {
      await stagehand.act(action);
    }
    ```

    To target a specific page:

    ```typescript
    const { data: actions } = await stagehand.observe("select blue as the favorite color", {
      page: page2,
    });
    const [action] = actions;

    if (action) {
      await stagehand.act(action, { page: page2 });
    }
    ```

    ## Extract

    Extract data from pages using natural language instructions. The `extract` method is called on the `stagehand` instance and always takes both an instruction and a schema.

    Every primitive returns `{ data, metadata }`. Your extracted value is on `data`; `metadata` carries the action ID and server-side cache status.

    ### Basic Extraction (with schema)

    ```typescript
    import { z } from "zod/v4";

    const { data } = await stagehand.extract(
      "extract all apartment listings with prices and addresses",
      z.object({
        listings: z.array(
          z.object({
            price: z.string(),
            address: z.string(),
          }),
        ),
      }),
    );

    console.log(data.listings);
    ```

    ### Simple Extraction

    A schema is always required, so wrap single values in an object:

    ```typescript
    const { data } = await stagehand.extract(
      "extract the sign in button text",
      z.object({ buttonText: z.string() }),
    );

    console.log(data.buttonText); // "Sign in"
    ```

    ### Targeted Extraction

    Scope extraction to a specific element with `locator`, and prune noise with `ignoreLocators`:

    ```typescript
    const { data } = await stagehand.extract(
      "extract the reason why script injection fails",
      z.object({ reason: z.string() }),
      {
        locator: page.locator("#main-content"),
        ignoreLocators: [page.locator("nav"), page.locator(".cookie-banner")],
      },
    );
    ```

    ### URL Extraction

    When extracting links or URLs, use `z.url()`:

    ```typescript
    const { data } = await stagehand.extract(
      "extract all navigation links",
      z.object({
        links: z.array(z.url()),
      }),
    );
    ```

    ### Extracting from a Specific Page

    ```typescript
    const { data } = await stagehand.extract(
      "extract the placeholder text on the name field",
      z.object({ placeholder: z.string() }),
      { page: page2 },
    );
    ```

    ### Inspecting Metadata

    ```typescript
    const TitleSchema = z.object({ title: z.string() });

    const result = await stagehand.extract("extract the page title", TitleSchema);

    console.log(result.data.title);
    console.log(result.metadata.actionId); // Action ID for tracing this call
    console.log(result.metadata.cache.status); // "HIT", "MISS", or "DISABLED"
    ```

    ## Observe

    Plan actions before executing them. Candidate actions are returned on `data`:

    ```typescript
    // Get candidate actions on the current active page
    const { data: actions } = await stagehand.observe("Click the sign in button");
    const [action] = actions;

    if (action) {
      console.log(action.selector, action.method, action.arguments);
    }
    ```

    Observing on a specific page:

    ```typescript
    const { data: actions } = await stagehand.observe("find the next page button", {
      page: page2,
    });
    await stagehand.act(actions[0], { page: page2 });
    ```

    ## Advanced Features

    ### Locators

    Use `page.locator(selector)` for deterministic, non-AI interactions. Selectors returned by `observe` are XPath strings prefixed with `xpath=`:

    ```typescript
    await page.locator("xpath=/html/body/div[2]/button").click();
    await page.locator("#email").fill("user@example.com");
    const count = await page.locator("li.result").count();
    ```

    ### Multi-Page Workflows

    ```typescript
    const page1 = await browser.context.newPage("https://example.com");

    const page2 = await browser.context.newPage("https://example2.com");

    // Act/extract/observe operate on the current active page by default
    // Pass { page } option to target a specific page
    await stagehand.act("click button", { page: page1 });
    await stagehand.extract("get title", z.object({ title: z.string() }), { page: page2 });
    ```

    ### Caching

    Server-side caching requires a Browserbase browser and a Browserbase API key:

    ```typescript
    const browser = await browserbase.launch({
      apiKey: process.env.BROWSERBASE_API_KEY,
    });
    const stagehand = await Stagehand.create({
      browser,
      cache: true, // or { threshold: 1 }
    });
    ```

    ## Cleanup

    Close Stagehand before closing its browser:

    ```typescript
    try {
      const stagehand = await Stagehand.create({ browser });
      try {
        // ...
      } finally {
        await stagehand.close();
      }
    } finally {
      await browser.close();
    }
    ```

    ## Project Structure Best Practices

    - Read configuration from environment variables and pass it explicitly to the `Stagehand` constructor
    - Create the browser first, pass it to `Stagehand.create()`, then pass the instance into your automation functions
    - Use `async`/`await` consistently; `create`, `act`, `extract`, `observe`, and `close` all return promises
    - Keep Zod schemas next to the code that consumes them and reuse `z.infer` for the decoded type
    - Wrap every workflow in `try`/`finally` so `close` runs even when a step throws
    - Prefer narrow, atomic instructions over one instruction that describes a whole workflow

    ## Security Notes

    - Never hard-code API keys. Read them from `process.env` and pass them explicitly; Stagehand reads no environment variables for you
    - Pass secrets through `variables` so they are substituted locally and never sent to the model provider
    - Set `logging: { level: "off" }` when handling sensitive data so nothing sensitive reaches your logs
    - Avoid broad instructions that may trigger unintended navigation; call `observe` first, then replay the returned `Action`

    ## Resources/References

    - TypeScript SDK: `@browserbasehq/stagehand` on npm
    - Stagehand documentation: https://docs.stagehand.dev
    - Stagehand Docs MCP (Mintlify): https://docs.stagehand.dev/mcp
    - Context7 MCP (Upstash): https://github.com/upstash/context7
    - DeepWiki MCP: https://mcp.deepwiki.com/
    ````
  </Tab>

  <Tab title="Python">
    ````md theme={null}
    # Stagehand Python Project

    This is a project that uses Stagehand v4 for Python, which provides AI-powered browser automation with `act`, `extract`, and `observe` methods.

    The main class can be imported as `Stagehand` from `stagehand`.

    **Key Classes:**

    - `Stagehand`: Main orchestrator class providing `act`, `extract`, and `observe` methods
    - `browser.context`: A `BrowserContext` object that manages pages, cookies, and the clipboard
    - `page`: Individual page objects accessed via `browser.context.active_page()`, `browser.context.pages()`, or created with `browser.context.new_page()`

    There is no `agent` API in v4. Compose `observe`, `act`, and `extract` in your own control flow instead.

    All Stagehand methods are async. `act`, `observe`, and `extract` take `instruction` (and, for
    `extract`, `schema`) positionally; every other argument is keyword-only.

    ## Initialize

    ```python
    import os

    from stagehand import Stagehand, browserbase, local_browser

    browser = await local_browser.launch(headless=True)
    stagehand = await Stagehand.create(
        browser=browser,
        model="openai/gpt-5.4-mini",
        model_api_key=os.environ["OPENAI_API_KEY"],
        logging={"level": "info", "format": "pretty"},
    )

    # Access the browser context and pages
    page = (await browser.context.pages())[0]
    context = browser.context

    # Create new pages if needed
    page2 = await browser.context.new_page()
    ```

    For Browserbase cloud browsers, pass a Browserbase API key:

    ```python
    browser = await browserbase.launch(
        api_key=os.environ["BROWSERBASE_API_KEY"],
    )
    stagehand = await Stagehand.create(browser=browser)
    ```

    With no model configured, Browserbase Model Gateway selects one automatically for each inference call.

    Stagehand never reads environment variables for you. Always pass keys explicitly.

    ## Act

    Actions are called on the `stagehand` instance (not the page). `act` takes either a string instruction or an `Action` from `observe`. Use atomic, specific instructions:

    ```python
    # Act on the current active page
    await stagehand.act("click the sign in button")

    # Act on a specific page (when you need to target a page that isn't currently active)
    await stagehand.act("click the sign in button", page=page2)
    ```

    **Important:** Act instructions should be atomic and specific:

    - Good: "Click the sign in button" or "Type 'hello' into the search input"
    - Bad: "Order me pizza" or "Type in the search bar and hit enter" (multi-step)

    Use `variables` for secrets. Values are substituted locally and never sent to the model:

    ```python
    await stagehand.act(
        "type %password% into the password field",
        variables={"password": os.environ["USER_PASSWORD"]},
    )
    ```

    ### Observe Then Act Pattern (Recommended)

    `act` accepts either a string instruction or an `Action` returned by `observe`. Use `observe` to inspect the candidate action, then pass it back to `act` for deterministic replay with no inference:

    ```python
    result = await stagehand.observe("Click the sign in button")
    action = result.data[0] if result.data else None

    if action is not None and action.method == "click":
        await stagehand.act(action)
    ```

    To target a specific page:

    ```python
    result = await stagehand.observe(
        "select blue as the favorite color",
        page=page2,
    )
    await stagehand.act(result.data[0], page=page2)
    ```

    ## Extract

    Extract data from pages using natural language instructions. The `extract` method is called on the `stagehand` instance and always takes both an instruction and a Pydantic model.

    Every primitive returns a result with `data` and `metadata`. Your extracted model instance is on `data`; `metadata` carries the action ID and server-side cache status.

    ### Basic Extraction (with schema)

    ```python
    from pydantic import BaseModel

    class Listing(BaseModel):
        price: str
        address: str

    class Listings(BaseModel):
        listings: list[Listing]

    result = await stagehand.extract(
        "extract all apartment listings with prices and addresses",
        Listings,
    )

    print(result.data.listings)
    ```

    ### Simple Extraction

    A schema is always required, so wrap single values in a model:

    ```python
    class ButtonText(BaseModel):
        button_text: str

    result = await stagehand.extract(
        "extract the sign in button text",
        ButtonText,
    )

    print(result.data.button_text)  # "Sign in"
    ```

    ### Targeted Extraction

    Scope extraction to a specific element with `locator`, and prune noise with `ignore_locators`:

    ```python
    class Reason(BaseModel):
        reason: str

    result = await stagehand.extract(
        "extract the reason why script injection fails",
        Reason,
        locator=page.locator("#main-content"),
        ignore_locators=[page.locator("nav"), page.locator(".cookie-banner")],
    )
    ```

    ### URL Extraction

    When extracting links or URLs, type the field as a URL:

    ```python
    from pydantic import AnyUrl, BaseModel

    class Links(BaseModel):
        links: list[AnyUrl]

    result = await stagehand.extract(
        "extract all navigation links",
        Links,
    )
    ```

    ### Extracting from a Specific Page

    ```python
    class Placeholder(BaseModel):
        placeholder: str

    result = await stagehand.extract(
        "extract the placeholder text on the name field",
        Placeholder,
        page=page2,
    )
    ```

    ### Inspecting Metadata

    ```python
    class Title(BaseModel):
        title: str

    result = await stagehand.extract(
        "extract the page title",
        Title,
    )

    print(result.data.title)
    print(result.metadata.action_id)  # Action ID for tracing this call
    print(result.metadata.cache.status)  # "HIT", "MISS", or "DISABLED"
    ```

    ## Observe

    Plan actions before executing them. Candidate actions are returned on `data`:

    ```python
    result = await stagehand.observe("Click the sign in button")
    action = result.data[0] if result.data else None

    if action is not None:
        print(action.selector, action.method, action.arguments)
    ```

    Observing on a specific page:

    ```python
    result = await stagehand.observe(
        "find the next page button",
        page=page2,
    )
    await stagehand.act(result.data[0], page=page2)
    ```

    ## Advanced Features

    ### Locators

    Use `page.locator(selector)` for deterministic, non-AI interactions. Selectors returned by `observe` are XPath strings prefixed with `xpath=`:

    ```python
    await page.locator("xpath=/html/body/div[2]/button").click()
    await page.locator("#email").fill("user@example.com")
    count = await page.locator("li.result").count()
    ```

    ### Multi-Page Workflows

    ```python
    page1 = await browser.context.new_page("https://example.com")

    page2 = await browser.context.new_page("https://example2.com")

    # Act/extract/observe operate on the current active page by default
    # Pass page= to target a specific page
    await stagehand.act("click button", page=page1)
    await stagehand.extract("get title", Title, page=page2)
    ```

    ### Caching

    Server-side caching requires a Browserbase browser and a Browserbase API key:

    ```python
    browser = await browserbase.launch(
        api_key=os.environ["BROWSERBASE_API_KEY"],
    )
    stagehand = await Stagehand.create(
        browser=browser,
        cache=True,  # or CacheOptions(threshold=1)
    )
    ```

    ## Cleanup

    Close Stagehand before closing its browser:

    ```python
    browser = await local_browser.launch(headless=True)
    try:
        stagehand = await Stagehand.create(browser=browser)
        try:
            ...
        finally:
            await stagehand.close()
    finally:
        await browser.close()
    ```

    ## Project Structure Best Practices

    - Store configurations in environment variables or config files
    - Use async/await patterns consistently
    - Implement main automation logic in async functions
    - Use async context managers for resource management
    - Use type hints and Pydantic models for data validation
    - Handle exceptions appropriately with try/except blocks

    ## Security Notes

    - Never hard-code API keys. Read them from `os.environ` and pass them explicitly; Stagehand reads no environment variables for you
    - Pass secrets through `variables` so they are substituted locally and never sent to the model provider
    - Set `logging={"level": "off"}` when handling sensitive data so nothing sensitive reaches your logs
    - Avoid broad instructions that may trigger unintended navigation; call `observe` first, then replay the returned `Action`

    ## Resources/References

    - Python SDK: `stagehand` on PyPI
    - Stagehand documentation: https://docs.stagehand.dev
    - Stagehand Docs MCP (Mintlify): https://docs.stagehand.dev/mcp
    - Context7 MCP (Upstash): https://github.com/upstash/context7
    - DeepWiki MCP: https://mcp.deepwiki.com/
    ````
  </Tab>

  <Tab title="Go">
    ````md theme={null}
    # Stagehand Go Project

    This is a project that uses Stagehand v4 for Go, which provides AI-powered browser automation with `Act`, `Extract`, and `Observe` methods.

    Import the package as `stagehand "github.com/browserbase/stagehand/packages/sdk-go"`.

    **Key Types:**

    - `*stagehand.Stagehand`: Main client, constructed with `stagehand.Create`, providing `Act`, `Extract`, and `Observe`
    - `*stagehand.BrowserContext`: Returned by `browser.Context()`, manages pages, cookies, and the clipboard
    - `*stagehand.Page`: Individual pages from `browserContext.ActivePage(ctx)`, `browserContext.Pages(ctx)`, or `browserContext.NewPage(ctx, url)`

    There is no agent API in v4. Compose `Observe`, `Act`, and `Extract` in your own control flow instead.

    Every method takes a `context.Context` as its first argument and returns an `error` as its last. Optional struct fields are pointers, so read values into variables and take their address.

    ## Initialize

    ```go
    apiKey := os.Getenv("OPENAI_API_KEY")
    model := stagehand.ModelConfig{
    	ModelName: "openai/gpt-5.4-mini",
    	APIKey:    &apiKey,
    }

    browser, err := stagehand.LaunchLocalBrowser(ctx, &stagehand.LocalBrowserLaunchOptions{Headless: true})
    if err != nil {
    	return err
    }
    defer func() { err = errors.Join(err, browser.Close(ctx)) }()
    client, err := stagehand.Create(ctx, stagehand.CreateOptions{
    	Browser: browser,
    	Model:   &model,
    })
    if err != nil {
    	return err
    }

    defer func() { err = errors.Join(err, client.Close(ctx)) }()

    // Access the browser context and pages
    browserContext, err := browser.Context()
    if err != nil {
    	return err
    }
    pages, err := browserContext.Pages(ctx)
    if err != nil {
    	return err
    }
    if len(pages) == 0 {
    	return errors.New("Stagehand initialized without an active page")
    }
    page := pages[0]

    // Create new pages if needed
    _, err = browserContext.NewPage(ctx, "https://example.com")
    if err != nil {
    	return err
    }

    // Target a specific page by passing it in the options
    if _, err := client.Act(ctx, stagehand.ActInstruction("click the sign in button"), &stagehand.StagehandClientActOptions{
    	Page: page,
    }); err != nil {
    	return err
    }
    ```

    For Browserbase cloud browsers, pass the Browserbase API key to `stagehand.LaunchBrowserbase()`:

    ```go
    browserbaseAPIKey := os.Getenv("BROWSERBASE_API_KEY")
    browser, err := stagehand.LaunchBrowserbase(ctx, stagehand.BrowserbaseLaunchOptions{
    	APIKey: browserbaseAPIKey,
    })
    if err != nil {
    	return err
    }
    client, err := stagehand.Create(ctx, stagehand.CreateOptions{
    	Browser: browser,
    	Model:   &model,
    })
    if err != nil {
    	return err
    }
    defer func() { err = errors.Join(err, client.Close(ctx)) }()
    ```

    Stagehand never reads environment variables for you. Always pass keys explicitly.

    ## Act

    Actions are called on the client (not the page). Use atomic, specific instructions:

    ```go
    // Act on the current active page
    if _, err := client.Act(ctx, stagehand.ActInstruction("click the sign in button"), nil); err != nil {
    	return err
    }

    // Act on a specific page (when you need to target a page that isn't currently active)
    if _, err := client.Act(ctx, stagehand.ActInstruction("click the sign in button"), &stagehand.StagehandClientActOptions{
    	Page: page2,
    }); err != nil {
    	return err
    }
    ```

    `Act` takes either a `string` instruction or a `stagehand.Action`. It returns an `ActResult`: the action payload is on `result.Data`, and cache status is on `result.Metadata`:

    ```go
    result, err := client.Act(ctx, stagehand.ActInstruction("click the sign in button"), nil)
    if err != nil {
    	return err
    }

    if !result.Data.Success {
    	return fmt.Errorf("act failed: %s", result.Data.Message)
    }
    ```

    **Important:** Act instructions should be atomic and specific:

    - Good: "Click the sign in button" or "Type 'hello' into the search input"
    - Bad: "Order me pizza" or "Type in the search bar and hit enter" (multi-step)

    Use variables for secrets. Values are substituted locally and never sent to the model:

    ```go
    password := os.Getenv("USER_PASSWORD")
    if _, err := client.Act(ctx, stagehand.ActInstruction("type %password% into the password field"), &stagehand.StagehandClientActOptions{
    	Variables: stagehand.Variables{
    		"password": stagehand.PrimitiveVariable(stagehand.StringVariable(password)),
    	},
    }); err != nil {
    	return err
    }
    ```

    ### Observe Then Act Pattern (Recommended)

    `Act` accepts either a string instruction or an `Action` returned by `Observe`. Use `Observe` to inspect the candidate action, then pass it back to `Act` for deterministic replay with no inference:

    ```go
    instruction := "Click the sign in button"
    observed, err := client.Observe(ctx, &instruction, nil)
    if err != nil {
    	return err
    }

    if len(observed.Data) > 0 && observed.Data[0].Method != nil && *observed.Data[0].Method == "click" {
    	if _, err := client.Act(ctx, stagehand.ObservedAction(observed.Data[0]), nil); err != nil {
    		return err
    	}
    }
    ```

    ## Extract

    Extract data from pages using natural language instructions. The generic `stagehand.Extract` function derives JSON Schema from the selected Go type and returns typed data plus metadata.

    ### Basic Extraction

    ```go
    type listing struct {
    	Price   string `json:"price"`
    	Address string `json:"address"`
    }

    type listings struct {
    	Listings []listing `json:"listings"`
    }

    data, err := stagehand.Extract[listings](
    	ctx,
    	client,
    	"extract all apartment listings with prices and addresses",
    	nil,
    )
    if err != nil {
    	return err
    }

    fmt.Println(data.Data.Listings)
    ```

    ### Typed Data and Metadata

    `Extract` decodes `Data` into the selected Go type and preserves the protocol metadata alongside it:

    ```go
    type buttonText struct {
    	ButtonText string `json:"buttonText"`
    }

    result, err := stagehand.Extract[buttonText](ctx, client, "extract the sign in button text", nil)
    if err != nil {
    	return err
    }

    fmt.Println(result.Data.ButtonText)
    fmt.Println(result.Metadata.Cache.Status)
    ```

    ### Targeted Extraction

    Scope extraction to a specific element with `Locator`, and prune noise with `IgnoreLocators`:

    ```go
    type article struct {
    	Body string `json:"body"`
    }

    result, err := stagehand.Extract[article](ctx, client, "extract the article body", &stagehand.StagehandClientExtractOptions{
    	Page:    page,
    	Locator: page.Locator("#main-content"),
    	IgnoreLocators: []*stagehand.PageLocator{
    		page.Locator("nav"),
    		page.Locator(".cookie-banner"),
    	},
    })
    if err != nil {
    	return err
    }

    fmt.Println(result.Data.Body)
    ```

    ### URL Extraction

    When extracting links or URLs, add JSON Schema constraints with `jsonschema` tags:

    ```go
    type link struct {
    	Text string `json:"text"`
    	URL  string `json:"url" jsonschema:"format=uri"`
    }

    type links struct {
    	Links []link `json:"links"`
    }
    ```

    ### Extracting from a Specific Page

    ```go
    type placeholder struct {
    	Placeholder string `json:"placeholder"`
    }

    result, err := stagehand.Extract[placeholder](ctx, client, "extract the placeholder text on the name field", &stagehand.StagehandClientExtractOptions{
    	Page: page2,
    })
    if err != nil {
    	return err
    }

    fmt.Println(result.Data.Placeholder)
    ```

    ### Inspecting Metadata

    `ActionID` is a pointer, so check for nil before dereferencing. `Cache` is always present:

    ```go
    type title struct {
    	Title string `json:"title"`
    }

    result, err := stagehand.Extract[title](ctx, client, "extract the page title", nil)
    if err != nil {
    	return err
    }

    if result.Metadata.ActionID != nil {
    	fmt.Println("action:", *result.Metadata.ActionID) // Action ID for tracing this call
    }
    fmt.Println("cache:", result.Metadata.Cache.Status) // HIT, MISS, or DISABLED
    ```

    ## Observe

    Plan actions before executing them. `Observe` takes an optional instruction pointer and returns candidate actions on `result.Data`:

    ```go
    instruction := "Click the sign in button"
    observed, err := client.Observe(ctx, &instruction, nil)
    if err != nil {
    	return err
    }

    for _, action := range observed.Data {
    	fmt.Println(action.Selector, action.Description)
    }
    ```

    Observing on a specific page:

    ```go
    instruction := "find the next page button"
    observed, err := client.Observe(ctx, &instruction, &stagehand.StagehandClientObserveOptions{
    	Page: page2,
    })
    if err != nil {
    	return err
    }
    if len(observed.Data) > 0 {
    	if _, err := client.Act(ctx, stagehand.ObservedAction(observed.Data[0]), &stagehand.StagehandClientActOptions{Page: page2}); err != nil {
    		return err
    	}
    }
    ```

    ## Advanced Features

    ### Locators

    Use `page.Locator(selector)` for deterministic, non-AI interactions. Selectors returned by `Observe` are XPath strings prefixed with `xpath=`:

    ```go
    if err := page.Locator("xpath=/html/body/div[2]/button").Click(ctx, nil); err != nil {
    	return err
    }
    if err := page.Locator("#email").Fill(ctx, "user@example.com"); err != nil {
    	return err
    }
    count, err := page.Locator("li.result").Count(ctx)
    if err != nil {
    	return err
    }
    fmt.Println("results:", count)
    ```

    ### Multi-Page Workflows

    ```go
    page1, err := browserContext.NewPage(ctx, "https://example.com")
    if err != nil {
    	return err
    }

    page2, err := browserContext.NewPage(ctx, "https://example2.com")
    if err != nil {
    	return err
    }

    // Act/Extract/Observe operate on the current active page by default
    // Pass Page in the options struct to target a specific page
    if _, err := client.Act(ctx, stagehand.ActInstruction("click button"), &stagehand.StagehandClientActOptions{Page: page1}); err != nil {
    	return err
    }
    if _, err := client.Act(ctx, stagehand.ActInstruction("click another button"), &stagehand.StagehandClientActOptions{Page: page2}); err != nil {
    	return err
    }
    ```

    ### Caching

    Server-side caching requires a Browserbase browser and a Browserbase API key:

    ```go
    cache := stagehand.CacheEnabled(true) // or stagehand.CacheWithThreshold(1)
    browser, err := stagehand.LaunchBrowserbase(ctx, stagehand.BrowserbaseLaunchOptions{
    	APIKey: browserbaseAPIKey,
    })
    if err != nil {
    	return err
    }
    client, err := stagehand.Create(ctx, stagehand.CreateOptions{
    	Browser: browser,
    	Cache:   &cache,
    })
    if err != nil {
    	return err
    }
    defer func() { err = errors.Join(err, client.Close(ctx)) }()
    ```

    ## Cleanup

    Always close the client. The idiomatic pattern is a named error return plus a deferred `errors.Join`:

    ```go
    func run(ctx context.Context) (err error) {
    	browser, err := stagehand.LaunchLocalBrowser(ctx, &stagehand.LocalBrowserLaunchOptions{Headless: true})
    	if err != nil {
    		return err
    	}
    	defer func() { err = errors.Join(err, browser.Close(ctx)) }()
    	client, err := stagehand.Create(ctx, stagehand.CreateOptions{Browser: browser})
    	if err != nil {
    		return err
    	}
    	defer func() { err = errors.Join(err, client.Close(ctx)) }()

    	// ...
    	return nil
    }
    ```

    ## Project Structure Best Practices

    - Read configuration from environment variables and pass it explicitly
    - Thread `context.Context` through every call so cancellation works
    - Check every returned `error`; do not discard them
    - Define extraction shapes once as Go types; `stagehand.Extract` derives JSON Schema from them
    - Use `jsonschema` struct tags for constraints such as formats, descriptions, and bounds

    ## Security Notes

    - Never hard-code API keys. Read them with `os.Getenv` and pass their addresses explicitly; Stagehand reads no environment variables for you
    - Pass secrets through `ActOptions.Variables` so they are substituted locally and never sent to the model provider
    - The Go client has no log level filter. Leave `Logging.OnLog` unset, or redact inside the handler, when handling sensitive data
    - Avoid broad instructions that may trigger unintended navigation; call `Observe` first, then replay the returned `Action`

    ## Resources/References

    - Go SDK: `github.com/browserbase/stagehand/packages/sdk-go`
    - Stagehand documentation: https://docs.stagehand.dev
    - Stagehand Docs MCP (Mintlify): https://docs.stagehand.dev/mcp
    - Context7 MCP (Upstash): https://github.com/upstash/context7
    - DeepWiki MCP: https://mcp.deepwiki.com/
    ````
  </Tab>
</Tabs>

## Security notes

* Do not embed secrets in docs or rule files; use environment variables in MCP configs.
* Pass secrets through `variables` so they are substituted locally and never sent to the model provider.
* Set `logging.level` to `"off"` when handling sensitive data so nothing sensitive reaches your logs.
* Avoid broad actions that may trigger unintended navigation; prefer `observe` first.

## Resources/references

* Context7 MCP (Upstash)
  * [https://github.com/upstash/context7](https://github.com/upstash/context7)
* DeepWiki MCP
  * [https://mcp.deepwiki.com/](https://mcp.deepwiki.com/)
* Stagehand Docs MCP (Mintlify)
  * [https://docs.stagehand.dev/mcp](https://docs.stagehand.dev/mcp)
