---
name: browse
description: Complete guide for creating and deploying browser automation functions using the stagehand CLI
homepage: https://browserbase.com
metadata: {"moltbot":{"emoji":"š","requires":{"bins":["stagehand"],"env":["BROWSERBASE_API_KEY","BROWSERBASE_PROJECT_ID"]},"primaryEnv":"BROWSERBASE_API_KEY"}}
---
# Browser Automation & Functions Skill
Complete guide for creating and deploying browser automation functions using the `stagehand` CLI.
## When to Use
- User wants to automate a website task
- User needs to scrape data from a site
- User wants to create a Browserbase Function
- User wants to deploy automation to run on a schedule or via webhook
## Prerequisites
### Set Up Credentials
```bash
stagehand fn auth status # Check if configured
stagehand fn auth login # If needed - get credentials from https://browserbase.com/settings
```
## Complete Workflow
### Step 1: Explore the Site Interactively
Start a local browser session to understand the site structure:
```bash
stagehand session create --local
stagehand goto https://example.com
stagehand snapshot # Get DOM structure with refs
stagehand screenshot -o page.png # Visual inspection
```
Test interactions manually:
```bash
stagehand click @0-5
stagehand fill @0-6 "value"
stagehand eval "document.querySelector('.price').textContent"
stagehand session end # When done exploring
```
### Step 2: Initialize Function Project
```bash
stagehand fn init my-automation
cd my-automation
```
Creates:
- `package.json` - Dependencies
- `.env` - Credentials (from `~/.stagehand/config.json`)
- `index.ts` - Function template
- `tsconfig.json` - TypeScript config
### Step 3: ā ļø FIX package.json IMMEDIATELY
**CRITICAL BUG**: `stagehand fn init` generates incomplete `package.json` that causes deployment to fail with "No functions were built."
**REQUIRED FIX** - Update `package.json` before doing anything else:
```json
{
"name": "my-automation",
"version": "1.0.0",
"description": "My automation description",
"main": "index.js",
"type": "module",
"packageManager": "pnpm@10.14.0",
"scripts": {
"dev": "pnpm bb dev index.ts",
"publish": "pnpm bb publish index.ts"
},
"dependencies": {
"@browserbasehq/sdk-functions": "^0.0.5",
"playwright-core": "^1.58.0"
},
"devDependencies": {
"@types/node": "^25.0.10",
"typescript": "^5.9.3"
}
}
```
**Key changes from generated file:**
- ā
Add `description` and `main` fields
- ā
Add `packageManager` field
- ā
Change `"latest"` to pinned versions like `"^0.0.5"`
- ā
Add `devDependencies` with TypeScript and types
Then install:
```bash
pnpm install
```
### Step 4: Write Automation Code
Edit `index.ts`:
```typescript
import { defineFn } from "@browserbasehq/sdk-functions";
import { chromium } from "playwright-core";
defineFn("my-automation", async (context) => {
const { session, params } = context;
console.log("Connecting to browser session:", session.id);
const browser = await chromium.connectOverCDP(session.connectUrl);
const page = browser.contexts()[0]!.pages()[0]!;
// Your automation here
await page.goto("https://example.com");
await page.waitForLoadState("domcontentloaded");
// Extract data
const data = await page.evaluate(() => {
// Complex extraction logic
return Array.from(document.querySelectorAll('.item')).map(el => ({
title: el.querySelector('.title')?.textContent,
value: el.querySelector('.value')?.textContent,
}));
});
// Return results (must be JSON-serializable)
return {
success: true,
count: data.length,
data,
timestamp: new Date().toISOString(),
};
});
```
**Key Concepts:**
- `context.session` - Browser session info (id, connectUrl)
- `context.params` - Input parameters from invocation
- Return JSON-serializable data
- 15 minute max execution time
### Step 5: Test Locally
Start dev server:
```bash
pnpm bb dev index.ts
```
Server runs at `http://127.0.0.1:14113`
Invoke with curl:
```bash
curl -X POST http://127.0.0.1:14113/v1/functions/my-automation/invoke \
-H "Content-Type: application/json" \
-d '{"params": {"url": "https://example.com"}}'
```
Dev server auto-reloads on file changes. Check terminal for logs.
### Step 6: Deploy to Browserbase
```bash
pnpm bb publish index.ts
# or: stagehand fn publish index.ts
```
**Expected output:**
```
ā Build completed successfully
Build ID: xxx-xxx-xxx
Function ID: yyy-yyy-yyy ā Save this!
```
**If you see "No functions were built"** ā Your package.json is incomplete (see Step 3).
### Step 7: Test Production
```bash
stagehand fn invoke <function-id> -p '{"param": "value"}'
```
Or via API:
```bash
curl -X POST https://api.browserbase.com/v1/functions/<function-id>/invoke \
-H "Content-Type: application/json" \
-H "x-bb-api-key: $BROWSERBASE_API_KEY" \
-d '{"params": {}}'
```
## Complete Working Example: Hacker News Scraper
```typescript
import { defineFn } from "@browserbasehq/sdk-functions";
import { chromium } from "playwright-core";
defineFn("hn-scraper", async (context) => {
const { session } = context;
console.log("Connecting to browser session:", session.id);
const browser = await chromium.connectOverCDP(session.connectUrl);
const page = browser.contexts()[0]!.pages()[0]!;
await page.goto("https://news.ycombinator.com");
await page.waitForLoadState("domcontentloaded");
// Extract top 10 stories
const stories = await page.evaluate(() => {
const storyRows = Array.from(document.querySelectorAll('.athing')).slice(0, 10);
return storyRows.map((row) => {
const titleLine = row.querySelector('.titleline a');
const subtext = row.nextElementSibling?.querySelector('.subtext');
const commentsLink = Array.from(subtext?.querySelectorAll('a') || []).pop();
return {
rank: row.querySelector('.rank')?.textContent?.replace('.', '') || '',
title: titleLine?.textContent || '',
url: titleLine?.getAttribute('href') || '',
points: subtext?.querySelector('.score')?.textContent?.replace(' points', '') || '0',
author: subtext?.querySelector('.hnuser')?.textContent || '',
time: subtext?.querySelector('.age')?.textContent || '',
comments: commentsLink?.textContent?.replace(/\u00a0comments?/, '').trim() || '0',
id: row.id,
};
});
});
return {
success: true,
count: stories.length,
stories,
timestamp: new Date().toISOString(),
};
});
```
## Common Patterns
### Parameterized Scraping
```typescript
defineFn("scrape", async (context) => {
const { session, params } = context;
const { url, selector } = params; // Accept params from invocation
const browser = await chromium.connectOverCDP(session.connectUrl);
const page = browser.contexts()[0]!.pages()[0]!;
await page.goto(url);
const data = await page.$eval(selector, els =>
els.map(el => el.textContent)
);
return { url, data };
});
```
### Authentication
```typescript
defineFn("auth-action", async (context) => {
const { session, params } = context;
const { username, password } = params;
const browser = await chromium.connectOverCDP(session.connectUrl);
const page = browser.contexts()[0]!.pages()[0]!;
await page.goto("https://example.com/login");
await page.fill('input[name="email"]', username);
await page.fill('input[name="password"]', password);
await page.click('button[type="submit"]');
await page.waitForURL("**/dashboard");
const data = await page.textContent('.user-data');
return { success: true, data };
});
```
### Multi-Page Workflow
```typescript
defineFn("multi-page", async (context) => {
const { session, params } = context;
const browser = await chromium.connectOverCDP(session.connectUrl);
const page = browser.contexts()[0]!.pages()[0]!;
const results = [];
for (const url of params.urls) {
await page.goto(url);
await page.waitForLoadState("domcontentloaded");
const title = await page.title();
results.push({ url, title });
}
return { results };
});
```
## Troubleshooting
### š“ "No functions were built. Please check your entrypoint and function exports."
**This is the #1 error!**
**Cause:** Generated `package.json` from `stagehand fn init` is incomplete.
**Fix:**
1. Update `package.json` (see Step 3 above)
2. Add all required fields: `description`, `main`, `packageManager`
3. Change `"latest"` to pinned versions like `"^0.0.5"`
4. Add `devDependencies` section with TypeScript and types
5. Run `pnpm install`
6. Try deploying again
**Quick check:** Compare your `package.json` to `bitcoin-functions/package.json` in the codebase.
### Local dev server won't start
```bash
# Check credentials
stagehand fn auth status
# Re-login if needed
stagehand fn auth login
# Install SDK globally
pnpm add -g @browserbasehq/sdk-functions
```
### Function works locally but fails on deploy
**Common causes:**
1. Missing `devDependencies` (TypeScript won't compile)
2. Using `"latest"` instead of pinned versions
3. Missing required fields in `package.json`
**Solution:** Fix package.json as described in Step 3.
### Cannot extract data from page
1. Take screenshot: `stagehand screenshot -o debug.png`
2. Get snapshot: `stagehand snapshot`
3. Use `page.evaluate()` to log what's in the DOM
4. Check if selectors match actual HTML structure
### "Invocation timed out"
- Functions have 15 minute max
- Use specific waits instead of long sleeps
- Check if page is actually loading
## Best Practices
1. ā
**Fix package.json immediately** after `stagehand fn init`
2. ā
**Explore interactively first** - Use local browser session to understand site
3. ā
**Test manually** - Verify each step works before writing code
4. ā
**Test locally** - Use dev server before deploying
5. ā
**Return meaningful data** - Include timestamps, counts, URLs
6. ā
**Handle errors gracefully** - Try/catch around risky operations
7. ā
**Use specific selectors** - Prefer data a
... (truncated)AI advertising agents that automates ad campaigns across Google Ads, Meta Ads, LinkedIn Ads, and TikTok Ads. Creates campaigns, reads live performance data, researches keywords with real CPC data, optimizes budgets, and manages ads through natural language via the Adspirer MCP server. 103 tools across 4 ad platforms.
Self-orchestrating multi-agent development workflows.
Comprehensive guide for building AI workflows, agents
General-purpose orchestration for Codex.