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

# Browserbase on Runloop

> Run browser agents from Runloop devboxes with Browserbase-managed browsers, connecting Playwright over CDP without installing Chromium in the devbox.

<Tip>
  The fastest path is the finished [Browserbase example](https://github.com/runloopai/runloop-examples/tree/main/browser-integrations/browserbase): clone it and run two commands. The sections after it explain how the example is built so you can adapt it. New to devboxes? Start with the [Quickstart](/docs/tutorials/quickstart).
</Tip>

Give a Runloop agent browser access with [Browserbase](https://www.browserbase.com): the agent runs in a devbox, the browser runs on Browserbase, and the devbox drives it by connecting Playwright over CDP to the session's connect URL, so no Chromium ever runs in the devbox.

In this guide:

* [Run the finished example](#run-the-finished-example): clone and run with two commands
* [Research across multiple pages](#research-across-multiple-pages): reuse one Browserbase session to scan multiple pages
* [AI actions with Stagehand](#ai-actions-with-stagehand): natural-language browser actions for agentic flows

## What you need

* A Runloop API key
* A Browserbase API key and project ID
* Python 3.12+ and `pip`, or Node.js 18+ and `npm`

## Environment variables

```bash theme={null}
export RUNLOOP_API_KEY=...
export BROWSERBASE_API_KEY=...
export BROWSERBASE_PROJECT_ID=...
```

<Note>
  Instead of exporting the Browserbase keys, store them as Runloop account secrets and map them into the devbox at runtime. See [Account Secrets](/docs/devboxes/configuration/account-secrets).
</Note>

## Run the finished example

Clone the repo, install dependencies, then create the blueprint and run the browser task.

<CodeGroup>
  ```bash Python theme={null}
  git clone https://github.com/runloopai/runloop-examples.git
  cd runloop-examples/browser-integrations/browserbase/python
  pip install -r requirements.txt
  python main.py create-blueprint
  python main.py run
  ```

  ```bash TypeScript theme={null}
  git clone https://github.com/runloopai/runloop-examples.git
  cd runloop-examples/browser-integrations/browserbase/typescript
  npm install
  npm run create-blueprint
  npm run run-browserbase
  ```
</CodeGroup>

`create-blueprint` bakes the Browserbase SDK and the Playwright client into a reusable blueprint, and `run` creates a devbox from it, uploads the agent, and drives a Browserbase browser. The rest of this guide walks through each of those pieces.

## How the example is built

To build it yourself, install the Runloop SDK locally. The devbox installs the Browserbase SDK and Playwright client itself (baked into the blueprint below), so nothing else is needed on your machine.

<CodeGroup>
  ```bash Python theme={null}
  pip install runloop_api_client
  ```

  ```bash TypeScript theme={null}
  npm install @runloop/api-client
  ```
</CodeGroup>

### Create a blueprint with the Browserbase SDK

Bake the Browserbase SDK and the Playwright client into a blueprint once so every devbox starts ready, with no install step. There is no `playwright install chromium`: the browser runs on Browserbase, so the devbox needs only the Playwright client library.

<CodeGroup>
  ```python Python theme={null}
  from runloop_api_client import AsyncRunloopSDK

  runloop = AsyncRunloopSDK()

  blueprint = await runloop.blueprint.create(
      name="browserbase-browser",
      system_setup_commands=["python3 -m pip install --user browserbase playwright"],
  )
  ```

  ```typescript TypeScript theme={null}
  import { RunloopSDK } from '@runloop/api-client';

  const runloop = new RunloopSDK();

  const blueprint = await runloop.blueprint.create({
    name: 'browserbase-browser',
    system_setup_commands: ['python3 -m pip install --user browserbase playwright'],
  });
  ```
</CodeGroup>

<Tip>
  From the example: `python main.py create-blueprint` or `npm run create-blueprint`. It is idempotent, so later runs reuse the built blueprint.
</Tip>

### Create a devbox and run a browser task

Create a devbox from the blueprint with the Browserbase keys injected, then have it create a Browserbase browser and drive it with Playwright over CDP. The agent connects to `session.connect_url` and runs ordinary Playwright code against the remote browser.

Here the agent returns structured JSON (title, headings, and link count) rather than a single string, which is the first useful browser primitive. The TypeScript version below orchestrates Runloop from Node, but the in-devbox agent is still Python, so the blueprint only needs the Python Browserbase SDK.

<CodeGroup>
  ```python Python theme={null}
  import os

  agent = '''
  import asyncio
  import os
  from browserbase import AsyncBrowserbase
  from playwright.async_api import async_playwright

  async def main():
      bb = AsyncBrowserbase()
      project_id = os.environ["BROWSERBASE_PROJECT_ID"]
      session = await bb.sessions.create(project_id=project_id)
      try:
          async with async_playwright() as pw:
              browser = await pw.chromium.connect_over_cdp(session.connect_url)
              try:
                  ctx = browser.contexts[0] if browser.contexts else await browser.new_context()
                  page = ctx.pages[0] if ctx.pages else await ctx.new_page()
                  await page.goto("https://docs.runloop.ai", wait_until="domcontentloaded")
                  data = await page.evaluate("""() => ({
                    title: document.title,
                    headings: document.querySelectorAll("h1, h2, h3").length,
                    links: document.querySelectorAll("a[href]").length,
                  })""")
                  print(data)
              finally:
                  await browser.close()
      finally:
          await bb.sessions.update(session.id, status="REQUEST_RELEASE", project_id=project_id)

  asyncio.run(main())
  '''

  devbox = await runloop.devbox.create_from_blueprint_name(
      "browserbase-browser",
      environment_variables={
          "BROWSERBASE_API_KEY": os.environ["BROWSERBASE_API_KEY"],
          "BROWSERBASE_PROJECT_ID": os.environ["BROWSERBASE_PROJECT_ID"],
      },
      launch_parameters={"resource_size_request": "SMALL"},
  )
  try:
      await devbox.file.write(file_path="/home/user/agent.py", contents=agent)
      result = await devbox.cmd.exec("python3 /home/user/agent.py")
      print(await result.stdout())
  finally:
      await devbox.shutdown()
  ```

  ```typescript TypeScript theme={null}
  const agent = `
  import asyncio
  import os
  from browserbase import AsyncBrowserbase
  from playwright.async_api import async_playwright

  async def main():
      bb = AsyncBrowserbase()
      project_id = os.environ["BROWSERBASE_PROJECT_ID"]
      session = await bb.sessions.create(project_id=project_id)
      try:
          async with async_playwright() as pw:
              browser = await pw.chromium.connect_over_cdp(session.connect_url)
              try:
                  ctx = browser.contexts[0] if browser.contexts else await browser.new_context()
                  page = ctx.pages[0] if ctx.pages else await ctx.new_page()
                  await page.goto("https://docs.runloop.ai", wait_until="domcontentloaded")
                  data = await page.evaluate("""() => ({
                    title: document.title,
                    headings: document.querySelectorAll("h1, h2, h3").length,
                    links: document.querySelectorAll("a[href]").length,
                  })""")
                  print(data)
              finally:
                  await browser.close()
      finally:
          await bb.sessions.update(session.id, status="REQUEST_RELEASE", project_id=project_id)

  asyncio.run(main())
  `;

  const devbox = await runloop.devbox.createFromBlueprintName('browserbase-browser', {
    environment_variables: {
      BROWSERBASE_API_KEY: process.env.BROWSERBASE_API_KEY!,
      BROWSERBASE_PROJECT_ID: process.env.BROWSERBASE_PROJECT_ID!,
    },
    launch_parameters: { resource_size_request: 'SMALL' },
  });

  try {
    await devbox.file.write({ file_path: '/home/user/agent.py', contents: agent });
    const result = await devbox.cmd.exec('python3 /home/user/agent.py');
    console.log(await result.stdout());
  } finally {
    await devbox.shutdown();
  }
  ```
</CodeGroup>

<Note>
  The Browserbase SDK and the Playwright client are the only dependencies the devbox needs. There is no Chromium install, because the browser runs on Browserbase and `connect_over_cdp` drives it remotely.
</Note>

<Tip>
  From the example: `python main.py run` or `npm run run-browserbase`.
</Tip>

## Research across multiple pages

To research several pages, reuse one Browserbase session: it avoids creating a session per page and preserves cookies and history. Connect Playwright once, define a reusable `scan` function that navigates and returns clean JSON, then call it for each URL. This code goes in the same in-devbox agent shown above.

<CodeGroup>
  ```python Python theme={null}
  import os
  from browserbase import AsyncBrowserbase
  from playwright.async_api import async_playwright

  bb = AsyncBrowserbase()
  session = await bb.sessions.create(project_id=os.environ["BROWSERBASE_PROJECT_ID"])
  try:
      async with async_playwright() as pw:
          browser = await pw.chromium.connect_over_cdp(session.connect_url)
          page = browser.contexts[0].pages[0]

          async def scan(url):
              await page.goto(url, wait_until="domcontentloaded", timeout=30000)
              return await page.evaluate("""() => {
                const clean = (s) => (s || "").replace(/\\s+/g, " ").trim();
                return {
                  title: clean(document.title),
                  headings: Array.from(document.querySelectorAll("h1, h2, h3"))
                    .map((e) => clean(e.textContent)).filter(Boolean).slice(0, 12),
                  links: document.querySelectorAll("a[href]").length,
                };
              }""")

          try:
              for url in ["https://runloop.ai", "https://docs.runloop.ai"]:
                  data = await scan(url)
                  print(data["title"], len(data["headings"]), "headings")
          finally:
              await browser.close()
  finally:
      await bb.sessions.update(
          session.id, status="REQUEST_RELEASE", project_id=os.environ["BROWSERBASE_PROJECT_ID"]
      )
  ```

  ```typescript TypeScript theme={null}
  import { chromium } from 'playwright-core';
  import Browserbase from '@browserbasehq/sdk';

  const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY });
  const session = await bb.sessions.create({ projectId: process.env.BROWSERBASE_PROJECT_ID! });
  try {
    const browser = await chromium.connectOverCDP(session.connectUrl);
    const page = browser.contexts()[0].pages()[0];

    async function scan(url: string) {
      await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
      return page.evaluate(() => {
        const clean = (s: string) => (s || '').replace(/\s+/g, ' ').trim();
        return {
          title: clean(document.title),
          headings: Array.from(document.querySelectorAll('h1, h2, h3'))
            .map((e) => clean(e.textContent ?? '')).filter(Boolean).slice(0, 12),
          links: document.querySelectorAll('a[href]').length,
        };
      });
    }

    try {
      for (const url of ['https://runloop.ai', 'https://docs.runloop.ai']) {
        const data = await scan(url);
        console.log(data.title, data.headings.length, 'headings');
      }
    } finally {
      await browser.close();
    }
  } finally {
    await bb.sessions.update(session.id, {
      status: 'REQUEST_RELEASE',
      projectId: process.env.BROWSERBASE_PROJECT_ID!,
    });
  }
  ```
</CodeGroup>

<Note>
  The TypeScript snippets connect with `playwright-core`, which has no bundled browser. That is the point: the browser runs on Browserbase, so the client needs only the Playwright protocol, not a local Chromium.
</Note>

## AI actions with Stagehand

When an agent should not hard-code selectors, use [Stagehand](https://github.com/browserbase/stagehand), Browserbase's AI browser-automation framework. You describe an action in natural language and a model resolves it against the live page at runtime. The primitives are `act` (do something), `extract` (pull typed data), and `observe` (see what's actionable). Stagehand runs on Browserbase and needs a model API key in addition to your Browserbase keys.

<CodeGroup>
  ```python Python theme={null}
  import os
  from stagehand import Stagehand

  # The `stagehand` PyPI package is the REST SDK: you call client.sessions.*.
  client = Stagehand(
      browserbase_api_key=os.environ["BROWSERBASE_API_KEY"],
      model_api_key=os.environ["MODEL_API_KEY"],
      server="remote",
  )

  started = client.sessions.start(model_name="openai/gpt-4.1-mini", browser={"type": "browserbase"})
  session_id = started.data.session_id
  try:
      client.sessions.navigate(session_id, url="https://docs.browserbase.com")
      client.sessions.act(session_id, input="click the link to the quickstart guide")
      result = client.sessions.extract(
          session_id,
          instruction="extract the page title and first heading",
          schema={
              "type": "object",
              "properties": {"title": {"type": "string"}, "heading": {"type": "string"}},
              "required": ["title", "heading"],
          },
      )
      print(result.data)
  finally:
      client.sessions.end(session_id)
  ```

  ```typescript TypeScript theme={null}
  // Stagehand v3: act/extract live on the instance; page comes from the context.
  import { Stagehand } from '@browserbasehq/stagehand';
  import { z } from 'zod';

  const stagehand = new Stagehand({
    env: 'BROWSERBASE',
    apiKey: process.env.BROWSERBASE_API_KEY,
    projectId: process.env.BROWSERBASE_PROJECT_ID,
    model: 'openai/gpt-4.1-mini',
  });

  await stagehand.init();
  const page = stagehand.context.pages()[0];
  try {
    await page.goto('https://docs.browserbase.com');
    await stagehand.act('click the link to the quickstart guide');
    const result = await stagehand.extract(
      'extract the page title and first heading',
      z.object({ title: z.string(), heading: z.string() }),
    );
    console.log(result);
  } finally {
    await stagehand.close();
  }
  ```
</CodeGroup>

<Note>
  Stagehand runs the browser automation on Browserbase, so the model issues fewer round-trips than thousands of individual CDP calls. It is the lower-chatter path for high-volume, multi-step flows.
</Note>

## How the integration works internally

1. A devbox boots from a blueprint with the Browserbase SDK and Playwright client already installed.
2. `BROWSERBASE_API_KEY` and `BROWSERBASE_PROJECT_ID` are injected into the devbox environment.
3. The agent calls `sessions.create()` to get a Browserbase cloud browser session.
4. It connects Playwright to the session with `chromium.connect_over_cdp(session.connect_url)` and drives the remote browser. No local Chromium.
5. Results return to the devbox; the orchestrator reads them and shuts the devbox down. The session is released with `sessions.update(..., status="REQUEST_RELEASE")`.

## Common issues

* **`resource_size_request` rejected**
  * The enum is upper-case (`SMALL`, `MEDIUM`, `LARGE`, ...). Lower-case values return a 400.
* **Browserbase auth errors inside the devbox**
  * Confirm both `BROWSERBASE_API_KEY` and `BROWSERBASE_PROJECT_ID` were passed via `environment_variables` when creating the devbox.
* **`connect_over_cdp` cannot connect**
  * Use `session.connect_url` from a freshly created session. A session that has timed out or been released will refuse the connection.
* **Advanced stealth or proxies rejected**
  * Proxies need the Developer plan; advanced stealth and verified mode need a top-tier plan. The API returns a 403 on lower plans; degrade to a plain session.

## Next Steps

* Full runnable source: the [Browserbase example](https://github.com/runloopai/runloop-examples/tree/main/browser-integrations/browserbase)
* Review [Devbox overview](/docs/devboxes/overview) and [Blueprints](/docs/devboxes/blueprints/overview)
* Read Browserbase's [Playwright quickstart](https://docs.browserbase.com/welcome/quickstarts/playwright) and [Stagehand](https://github.com/browserbase/stagehand) docs
