Complete guide for creating and deploying browser automation functions using the stagehand CLI
Complete guide for creating and deploying browser automation functions using the `stagehand` CLI.
```bash
stagehand fn auth status # Check if configured
stagehand fn auth login # If needed - get credentials from https://browserbase.com/settings
```
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
```
```bash
stagehand fn init my-automation
cd my-automation
```
Creates:
**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": "[email protected]",
"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:**
Then install:
```bash
pnpm install
```
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:**
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.
```bash
pnpm bb 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).
```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": {}}'
```
```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(),
};
});
```
```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 };
});
```
```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 };
});
```
```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 };
});
```
**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.
```bash
stagehand fn auth status
stagehand fn auth login
pnpm add -g @browserbasehq/sdk-functions
```
**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.
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
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 attributes over CSS classes
8. ā **Add logging** - `console.log()` helps debug deployed functions
9. ā **Validate parameters** - Check `params` before using
10. ā **Set reasonable timeouts** - Don't wait forever
**File:** `/src/commands/functions.ts`
**Lines:** 146-158
**Function:** `initFunction()`
Replace the current `packageJson` object with:
```typescript
const packageJson = {
name,
version: '1.0.0',
description: `${name} function`,
main: 'index.js',
type: 'module',
packageManager: '[email protected]',
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.
Leave a review
No reviews yet. Be the first to review this skill!