← Back to blog

    March 15, 2026 · 4 min read

    What is BrowserGateway?

    The open-source router for cloud browsers. One connection URL. Every provider. MIT license.

    If you're building anything with headless browsers at scale, you end up running more than one provider. Browserless, Steel, Browserbase, Cloudflare Browser Rendering, self-hosted Chrome. Different jobs pull you toward different backends. Each provider is tuned for something specific: some for stealth, some for raw capacity, some for the lowest cost floor. Picking one meant inheriting its limitations everywhere else. Adding a second provider meant rewriting your connection code, re-implementing failover, re-storing cookies. That layer belongs one level down from your app code.

    BrowserGateway is a router that sits in front of all of them so you write one connection URL and swap providers without changing a line of app code.

    It's MIT-licensed. You self-host it, or you use the hosted version at browsergateway.com. Same code either way. Stagehand, browser-use, Puppeteer, Playwright, and any MCP client all connect the same way.

    The one-liner

    Think OpenRouter, but for cloud browsers instead of LLMs. Same shape. Different domain.

    What you get

    Install it in two commands:

    npm install -g browser-gateway
    browser-gateway serve

    Or run the Docker image:

    docker run -p 9500:9500 ghcr.io/browser-gateway/server:latest

    Put your providers in gateway.yml:

    backends:
      browserless-1:
        url: wss://production-sfo.browserless.io/?token=${BROWSERLESS_TOKEN}
        limits:
          maxConcurrent: 5
        priority: 1
      steel-1:
        url: wss://connect.steel.dev?apiKey=${STEEL_KEY}
        limits:
          maxConcurrent: 2
        priority: 2
      local-chrome:
        url: ws://localhost:9222
        priority: 3

    Then your app connects to one URL:

    import puppeteer from "puppeteer-core";
     
    const browser = await puppeteer.connect({
      browserWSEndpoint: "ws://localhost:9500/v1/connect",
    });
     
    const page = await browser.newPage();
    await page.goto("https://en.wikipedia.org/");
    await page.screenshot({ path: "wiki.png" });

    The gateway picks the best available provider based on your priority ordering, real-time capacity, latency, and cost. If Browserless is saturated, it falls over to Steel. If Steel is in cooldown from a health-check failure, it falls over to local Chrome. Your code doesn't notice.

    What it does beyond routing

    • Persistent browser profiles. Cookies, localStorage, IndexedDB captured at session end and injected on the next connect. Same profile survives across providers. Encrypted at rest with a key you control.
    • Session replay. CDP screencast frames captured throughout a session, encoded to MP4 in the background, playable in the dashboard. Debugging failed agent runs stops being guesswork.
    • MCP server built in, exposed at POST /mcp. AI agents (Claude, Cursor, any MCP client) request browsers through the same routing layer.
    • REST API for one-shot screenshot, content, and scrape. Sits atop a pooled internal Chrome.
    • Dashboard at http://localhost:9500/web with providers, sessions, profiles, replays, config editor, live playground.
    • Five routing strategies: priority chain, round-robin, least-connections, weighted, latency-optimized. Configurable at runtime.
    • Webhooks on session start / end / recorded / profile updated.

    Why we built it

    Lopsided provider strengths and limitations. Providers optimize for different things. Some are tuned for stealth. Some hit raw capacity levels others can't. Some sit at the lowest cost floor. But every strength comes with a matching limitation somewhere else. Picking one meant inheriting its limitations across every workload, and any app code that depended on a provider-specific quirk had to be rewritten whenever we wanted to swap.

    Vendor lock-in that followed. Once the code married a specific provider's shape, moving off it meant a rewrite. That was the biggest cost. Occasional downtime made it worse: when a provider had a bad day, our app went down with them, and the only fallback was more code.

    The routing layer was the answer to both. Use each provider for what it's best at. Swap without touching app code. The backend becomes plug-and-play. End users feel nothing when the underlying provider changes.

    We looked around for what existed. Browserless is a browser provider, not a router. Steel is a browser provider, not a router. Browserbase is a browser provider, not a router. Nothing sat where the routing layer needed to sit. So we built it.

    A second use case emerged as we built. If you're benchmarking a website, checking the reliability of a source, or running automation research, being able to hit the same URL through several different browser providers gives you signal you can't get from one. The router turned out to be as useful for cross-provider testing as it was for production traffic.

    Provider-friendly by design

    BrowserGateway routes to the providers you already pay for. We don't operate browsers ourselves. We're not competing with the browser services. We complement them by making it painless to run more than one at once.

    The repo is on GitHub under MIT. Try it. Tell us what breaks.