Browserbase
Scanned@pkiv
npx machina-cli add skill @pkiv/browse --openclawBrowser 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
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:
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:
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
stagehand fn init my-automation
cd my-automation
Creates:
package.json- Dependencies.env- Credentials (from~/.stagehand/config.json)index.ts- Function templatetsconfig.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:
{
"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
descriptionandmainfields - ✅ Add
packageManagerfield - ✅ Change
"latest"to pinned versions like"^0.0.5" - ✅ Add
devDependencieswith TypeScript and types
Then install:
pnpm install
Step 4: Write Automation Code
Edit index.ts:
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:
pnpm bb dev index.ts
Server runs at http://127.0.0.1:14113
Invoke with curl:
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
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
stagehand fn invoke <function-id> -p '{"param": "value"}'
Or via API:
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
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
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
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
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:
- Update
package.json(see Step 3 above) - Add all required fields:
description,main,packageManager - Change
"latest"to pinned versions like"^0.0.5" - Add
devDependenciessection with TypeScript and types - Run
pnpm install - Try deploying again
Quick check: Compare your package.json to bitcoin-functions/package.json in the codebase.
Local dev server won't start
# 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:
- Missing
devDependencies(TypeScript won't compile) - Using
"latest"instead of pinned versions - Missing required fields in
package.json
Solution: Fix package.json as described in Step 3.
Cannot extract data from page
- Take screenshot:
stagehand screenshot -o debug.png - Get snapshot:
stagehand snapshot - Use
page.evaluate()to log what's in the DOM - 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
- ✅ Fix package.json immediately after
stagehand fn init - ✅ Explore interactively first - Use local browser session to understand site
- ✅ Test manually - Verify each step works before writing code
- ✅ Test locally - Use dev server before deploying
- ✅ Return meaningful data - Include timestamps, counts, URLs
- ✅ Handle errors gracefully - Try/catch around risky operations
- ✅ Use specific selectors - Prefer data attributes over CSS classes
- ✅ Add logging -
console.log()helps debug deployed functions - ✅ Validate parameters - Check
paramsbefore using - ✅ Set reasonable timeouts - Don't wait forever
Quick Checklist
- Explore site with
stagehand session create --local - Test interactions manually
- Create project:
stagehand fn init <name> - Fix package.json immediately (Step 3)
- Run
pnpm install - Write automation in
index.ts - Test locally:
pnpm bb dev index.ts - Verify with curl
- Deploy:
pnpm bb publish index.ts - Test production:
stagehand fn invoke <function-id> - Save function ID
Code Fix Needed (For Maintainers)
File: /src/commands/functions.ts
Lines: 146-158
Function: initFunction()
Replace the current packageJson object with:
const packageJson = {
name,
version: '1.0.0',
description: `${name} function`,
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',
},
};
This will eliminate the "No functions were built" error for all new projects.
Overview
Browserbase provides a complete guide to creating and deploying browser automation functions using the Stagehand CLI. It covers interactive site exploration, function project setup, code writing, and local testing to enable reliable automated tasks and data extraction.
How This Skill Works
Use Stagehand to start a local browser session, explore the site structure, and test interactions. Then scaffold a function with stagehand fn init, modify package.json as guided, and implement the automation in TypeScript using the Browserbase SDK and Playwright. Your function runs in a defined environment and returns JSON-serializable data, with a 15-minute execution limit.
When to Use It
- Automate a website task
- Scrape data from a site
- Create a Browserbase Function
- Deploy automation to run on a schedule or via webhook
- Prototype and test flows in a local session
Quick Start
- Step 1: Start a local browser session and explore the site with Stagehand
- Step 2: Run stagehand fn init my-automation and open the project folder
- Step 3: Fix package.json per the guide, install dependencies, and implement index.ts
Best Practices
- Start with interactive site exploration using a local browser session
- Initialize a function project with stagehand and customize file structure
- Apply the critical package.json fixes before deployment
- Ensure the function returns JSON-serializable data and stays under 15 minutes
- Test locally with pnpm bb dev and validate deployment readiness
Example Use Cases
- Automate login to a dashboard and scrape KPI metrics
- Scrape product prices across multiple pages for comparison
- Run a daily stock status check and push webhook alerts
- Automate form submissions for routine data entry tasks
- Render and capture pages for archival and QA checks