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

# Quickstart

> Build, test, publish, and invoke a Browserbase Function from a local TypeScript project.

This tutorial deploys a Stagehand script as a Function, gives it your API key through a Secret, then invokes it through the Browserbase API.

<Warning>
  Functions are currently only available in the **us-west-2** region.
</Warning>

## Prerequisites

You need:

* A Browserbase account
* A Browserbase API key from [Settings](https://www.browserbase.com/settings)
* Node.js and pnpm 10 or later
* The `browse` CLI:

```bash theme={null}
npm install -g browse
```

## Create a Functions project

<Steps>
  <Step title="Initialize the project">
    Create the project:

    ```bash theme={null}
    browse functions init my-functions-project
    cd my-functions-project
    ```

    The initializer installs Stagehand and creates `package.json`, `tsconfig.json`, `.env`, and a starter `index.ts`.
  </Step>

  <Step title="Add your credentials">
    Export your Browserbase API key from [Settings](https://www.browserbase.com/settings) in the terminal where you run the development server:

    ```bash theme={null}
    export BROWSERBASE_API_KEY=your_api_key
    ```

    Browserbase resolves the project from the API key. You don't need a Project ID. You also don't need a model API key, because Stagehand sends model calls through the [Browserbase Model Gateway](https://docs.browserbase.com/platform/model-gateway/overview#model-gateway). Deployed Functions read the API key from a [Secret](#add-the-secret) instead.
  </Step>

  <Step title="Add the Stagehand extension">
    Stagehand needs its extension in the browser session. Upload the extension to your project:

    ```bash theme={null}
    browse cloud extensions upload node_modules/@browserbasehq/stagehand/dist/assets/stagehand-extension.zip
    ```

    Copy the `id` from the output. The Function uses it in the next step. If you use Playwright, skip this step.
  </Step>

  <Step title="Define the Function">
    Replace `index.ts` with a Function that returns the title of a page. For Stagehand, replace `your-extension-id` with the `id` from the previous step:

    <Tabs>
      <Tab title="Stagehand">
        ```typescript Node.js theme={null}
        import { defineFn } from "@browserbasehq/sdk-functions";
        import { browserbase, Stagehand } from "@browserbasehq/stagehand";
        import { z } from "zod/v4";

        defineFn(
          "page-title",
          async (context, params) => {
            const browser = await browserbase.connect({
              // The local dev server has no Secrets, so fall back to .env.
              apiKey: context.secrets.BROWSERBASE_API_KEY ?? process.env.BROWSERBASE_API_KEY!,
              sessionId: context.session.id,
            });
            // In this example, Stagehand uses the Model Gateway where Browserbase charges for the tokens
            const stagehand = await Stagehand.create({ browser });
            const page = (await browser.context.activePage())!;

            await page.goto(params.url);
            const { data } = await stagehand.extract(
              "Extract the page title",
              z.object({ title: z.string() }),
            );

            await stagehand.close();
            return { title: data.title };
          },
          {
            parametersSchema: z.object({
              url: z.string().url(),
            }),
            sessionConfig: { extensionId: "your-extension-id" },
          },
        );
        ```
      </Tab>

      <Tab title="Playwright">
        ```typescript Node.js theme={null}
        import { defineFn } from "@browserbasehq/sdk-functions";
        import { chromium } from "playwright-core";
        import { z } from "zod";

        defineFn(
          "page-title",
          async (context, params) => {
            const browser = await chromium.connectOverCDP(
              context.session.connectUrl,
            );
            const page = browser.contexts()[0]!.pages()[0]!;

            await page.goto(params.url);

            return { title: await page.title() };
          },
          {
            parametersSchema: z.object({
              url: z.string().url(),
            }),
          },
        );
        ```
      </Tab>
    </Tabs>
  </Step>
</Steps>

<Warning>
  The initializer creates a `pnpm-workspace.yaml` file in the project directory. It holds the pnpm setting that approves the esbuild build script, which pnpm 11 and later require.
</Warning>

## Test the Function locally

<Steps>
  <Step title="Start the development server">
    Run:

    ```bash theme={null}
    browse functions dev index.ts
    ```

    The server listens on `http://127.0.0.1:14113` and reloads when you change the Function files.
  </Step>

  <Step title="Invoke the local Function">
    In another terminal, invoke the Function by the name passed to `defineFn`:

    ```bash theme={null}
    curl --request POST \
      --url http://127.0.0.1:14113/v1/functions/page-title/invoke \
      --header 'Content-Type: application/json' \
      --data '{"params":{"url":"https://example.com"}}'
    ```

    The local server runs the handler synchronously. The response contains:

    ```json theme={null}
    {
      "title": "Example Domain"
    }
    ```
  </Step>
</Steps>

## Publish the Function

Publish the entrypoint:

```bash theme={null}
browse functions publish index.ts
```

The command builds each Function reachable from `index.ts` and prints the Function IDs. Save the ID for `page-title` from `builtFunctions[].id`. See [Deploy Functions](/platform/functions/deploy#deploy-from-the-cli) for multi-file projects.

## Add the Secret

The Stagehand version reads your Browserbase API key from a Secret. If you use Playwright, skip this section.

Create a project Secret for your Browserbase API key. The `--env` flag reads the value from your shell environment, so export the key first:

```bash theme={null}
export BROWSERBASE_API_KEY=your_api_key
browse cloud secrets create BROWSERBASE_API_KEY --env BROWSERBASE_API_KEY
```

Attach the Secret to the Function with the Function ID and the `id` of the Secret:

```bash theme={null}
browse functions secrets attach FUNCTION_ID SECRET_ID
browse functions secrets list FUNCTION_ID
```

The `list` output must show the Secret.

If the build fails, open the build in the Browserbase dashboard to see the build logs. The logs show the install and build errors.

## Invoke the deployed Function

Production invocations run asynchronously. Start an invocation with the Function ID:

```bash theme={null}
curl --request POST \
  --url https://api.browserbase.com/v1/functions/FUNCTION_ID/invoke \
  --header 'Content-Type: application/json' \
  --header "x-bb-api-key: $BROWSERBASE_API_KEY" \
  --data '{"params":{"url":"https://example.com"}}'
```

The API returns `202 Accepted` with an invocation object. Copy its `id`, then poll the invocation:

```bash theme={null}
curl --request GET \
  --url https://api.browserbase.com/v1/functions/invocations/INVOCATION_ID \
  --header "x-bb-api-key: $BROWSERBASE_API_KEY"
```

When `status` is `COMPLETED`, `results` contains the value returned by the handler.

## Next steps

<CardGroup cols={2}>
  <Card title="Write a Function" icon="code" href="/platform/functions/write">
    Configure parameters, browser sessions, and multiple Functions.
  </Card>

  <Card title="Deploy Functions" icon="cloud-arrow-up" href="/platform/functions/deploy">
    Publish from the CLI or the Playground.
  </Card>

  <Card title="Invoke a Function" icon="terminal" href="/platform/functions/invoke">
    Override session settings and handle asynchronous results.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.