Route Stagehand's local browser through ProxyHat residential proxies — a pinned sticky IP per agent session, geo-targeting, and rotating IPs.
Tip
Recommended proxies — ProxyHat residential IPs. Every feature in this package is tested end-to-end against ProxyHat and works great. First-class integration; also works with any proxy, or none.
Stagehand drives an AI agent over a browser (Playwright under the hood). When you run it locally (env: "LOCAL"), that browser goes out from your own datacenter IP — which gets your agent blocked, CAPTCHA'd, and rate-limited. This package plugs ProxyHat's residential IPs (50M+ across 148+ countries) into Stagehand's local browser through its first-class proxy option, and maps each Stagehand instance to one pinned residential IP for its whole agent session — mimicking a real user. No boilerplate.
npm install stagehand-proxyhat@browserbasehq/stagehand is a peer dependency — bring your own version (>=2).
import { Stagehand } from "@browserbasehq/stagehand";
import { proxyhatStagehandConfig } from "stagehand-proxyhat";
// An API key auto-selects an active residential sub-user. `env` defaults to
// "LOCAL"; your own Stagehand config (model, systemPrompt, …) is preserved.
const config = await proxyhatStagehandConfig(
{ apiKey: process.env.PROXYHAT_API_KEY, targeting: { country: "us" } },
{ model: "openai/gpt-4o" },
);
const stagehand = new Stagehand(config);
await stagehand.init();
const page = stagehand.context.pages()[0];
await page.goto("https://httpbin.org/ip"); // exits from a US residential IPGet an API key at proxyhat.com.
Stagehand's local browser reads its proxy from localBrowserLaunchOptions.proxy (a Playwright-shaped { server, username, password, bypass }). proxyhatStagehandConfig returns a full Stagehand constructor config (v3 V3Options, formerly ConstructorParams) with that field filled in; proxyhatProxy returns just the proxy object if you'd rather set it yourself.
The proxy only applies to
env: "LOCAL". Inenv: "BROWSERBASE"the browser runs in Browserbase's cloud and proxies are configured there instead —proxyhatStagehandConfigpreserves whateverenvyou pass but the proxy field is only honoured locally.
Pass them explicitly or via environment variables — options win over env:
| Option | Env var | Notes |
|---|---|---|
apiKey |
PROXYHAT_API_KEY |
Auto-selects an active sub-user with remaining traffic |
subUser |
PROXYHAT_SUBUSER |
Pick a specific sub-user by uuid or name (with an API key) |
username |
PROXYHAT_USERNAME |
Explicit gateway proxy_username (skips the API) |
password |
PROXYHAT_PASSWORD |
Explicit gateway proxy_password |
const config = await proxyhatStagehandConfig({
apiKey: process.env.PROXYHAT_API_KEY,
protocol: "http", // or "socks5"
targeting: {
country: "us", // ISO code or "any" (default)
region: "california",
city: "new_york",
filter: "high", // AI IP-quality tier
},
stickyTtl: "30m", // sticky-session lifetime (default "30m")
});By default this package pins each Stagehand instance to one ProxyHat residential IP: a fresh sticky id is minted per call, so every request the agent makes during its session keeps that same IP, like a real user. Close the browser and the IP is dropped. Build a second Stagehand instance and it gets its own distinct pinned IP.
Want a fresh IP on every request instead? Turn stickiness off — the gateway rotates the exit IP per connection:
const config = await proxyhatStagehandConfig({ apiKey, sticky: false });Already have a Stagehand config? Use proxyhatProxy for just the proxy object and drop it into localBrowserLaunchOptions.proxy:
import { Stagehand } from "@browserbasehq/stagehand";
import { proxyhatProxy } from "stagehand-proxyhat";
const proxy = await proxyhatProxy({ apiKey: process.env.PROXYHAT_API_KEY, targeting: { country: "us" } });
const stagehand = new Stagehand({
env: "LOCAL",
model: "openai/gpt-4o",
localBrowserLaunchOptions: { headless: false, proxy },
});
await stagehand.init();Fully offline and synchronous once you have credentials:
import { buildStagehandProxy, resolveCredentials } from "stagehand-proxyhat";
const credentials = await resolveCredentials({ apiKey: process.env.PROXYHAT_API_KEY });
const proxy = buildStagehandProxy(credentials, { targeting: { country: "us" } });
// -> { server: "http://gate.proxyhat.com:8080", username: "…-country-us-sid-…-ttl-30m", password: "…" }proxyhatProxy (and proxyhatStagehandConfig) resolves your gateway credentials once (via the official proxyhat SDK), then builds the proxy object Stagehand's local browser expects: server points at the ProxyHat gateway (gate.proxyhat.com:8080 for HTTP, :1080 for SOCKS5) and username carries the targeting grammar. With stickiness on it mints a sid/ttl so the session keeps one pinned IP; with sticky: false it omits the sid so the gateway hands out a fresh residential IP per connection.
MIT © ProxyHat