7.3k★by pkiv
browse – OpenClaw Skill
browse is an OpenClaw Skills integration for coding workflows. Complete guide for creating and deploying browser automation functions using the stagehand CLI
Skill Snapshot
| name | browse |
| description | Complete guide for creating and deploying browser automation functions using the stagehand CLI OpenClaw Skills integration. |
| owner | pkiv |
| repository | pkiv/browse |
| language | Markdown |
| license | MIT |
| topics | |
| security | L1 |
| install | openclaw add @pkiv/browse |
| last updated | Feb 7, 2026 |
Maintainer

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
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.
Browserbase Skills
Claude skill files for browser automation with Browserbase.
Skills
- SKILL.md - Main skill: Browser Automation & Functions
- skills/auth/ - Authentication flows
- skills/create/ - Creating new automations
- skills/fix/ - Debugging and fixing automations
- skills/functions/ - Deploying Browserbase Functions
Usage
Add these skill files to your Claude project or cursor rules to enable browser automation capabilities.
Permissions & Security
Security level L1: Low-risk skills with minimal permissions. Review inputs and outputs before running in production.
Requirements
### Set Up Credentials ```bash stagehand fn auth status # Check if configured stagehand fn auth login # If needed - get credentials from https://browserbase.com/settings ```
FAQ
How do I install browse?
Run openclaw add @pkiv/browse in your terminal. This installs browse into your OpenClaw Skills catalog.
Does this skill run locally or in the cloud?
OpenClaw Skills execute locally by default. Review the SKILL.md and permissions before running any skill.
Where can I verify the source code?
The source repository is available at https://github.com/openclaw/skills/tree/main/skills/pkiv/browse. Review commits and README documentation before installing.
