For AI agents: a documentation index is available at /llms.txt
Skip to main content

Create a browser session

A session is a browser that stays open between commands, so cookies, logins, and navigation history survive from one step to the next. This page opens one with BAP, our SDK, and covers the whole lifecycle: open it, work in it, keep it alive past your own connection, and close it.

One-shot jobs don't need any of this. If you just want the contents of a URL, scrape a website URL is a single call.

Steps

  1. Get Your API Token

    Sign up for a free account, then copy your API token from the account dashboard.

  2. Install the SDK

    npm install @browserless.io/bap-ts
  3. Open and Close a Session

    connect() opens no socket on its own. The session starts when you ask for a page, and it runs until you close the browser or it times out.

    import Browserless from "@browserless.io/bap-ts";

    const TOKEN = "YOUR_API_TOKEN_HERE";

    const browser = Browserless.connect({
    browserWSEndpoint: "wss://production-sfo.browserless.io/chromium/bql",
    token: TOKEN,
    });

    try {
    // The WebSocket opens here, not on connect().
    const page = await browser.newPage();
    await page.goto("https://example.com");

    console.log(await page.title());
    } finally {
    // Always close. An abandoned session bills until it times out.
    await browser.close();
    }

    Output

    Example Domain

    The endpoint must end in /bql. The same host also serves CDP paths such as /chromium, and those don't speak BrowserQL. Swap the path for /stealth/bql when a site is challenging you.

Run several steps against the same page

This is what the session buys you. Each call lands on the page the last one left behind, so a login holds for everything after it.

await page.goto("https://example.com/login");

await page.type("#email", "user@example.com");
await page.type("#password", "hunter2");
await page.click("button[type=submit]");

// Same browser, same cookies: the session is authenticated from here on.
await page.goto("https://example.com/account");
const { text } = await page.text({ selector: "h1" });
console.log(text);

Output

Your account

Save what the session earned with cookies() and replay it into a later session to skip the login entirely. See cookies and page setup.

Keep the session alive past your connection

By default the browser shuts down when your page's WebSocket closes. reconnect() changes that: the browser waits for a new connection for as long as you specify, and you get back the endpoints to return to it.

Call it while the page is still open, before the finally block from the steps above runs. Once browser.close() has returned there's no live connection left to extend. Closing the original connection after a successful reconnect() is fine and doesn't cancel the waiting window.

// After this, the browser survives 60 seconds waiting for a reconnect.
const session = await page.reconnect({ timeout: 60_000 });

// Returned as https://; BAP talks BrowserQL over WebSocket.
const endpoint = session.browserQLEndpoint!.replace(/^https/, "wss");

const resumed = Browserless.connect({
browserWSEndpoint: endpoint,
token: TOKEN,
});

try {
const samePage = await resumed.newPage();
// Same browser, same state: still on the account page.
console.log(await samePage.url());
} finally {
await resumed.close();
}

Output

https://example.com/account

The reconnected page is still on the account page from the previous section, which is the point: the browser kept its state while nothing was connected to it. That's how a session survives a client restart or moves between processes. Alongside browserQLEndpoint, reconnect() returns browserWSEndpoint for rejoining with Puppeteer or Playwright over CDP, and devtoolsFrontendUrl for opening the live browser in DevTools. See reconnecting to sessions.

Watch the session while it runs

liveURL() returns a shareable link that streams the browser. Pass interactable: true and the viewer can click and type, which is how you hand a one-time passcode or a stubborn login to a person mid-run.

const { liveURL } = await page.liveURL({ interactable: true });
console.log(`Hand off to a human: ${liveURL}`);

// Block until the person's part is done, then carry on.
await page.waitForSelector("#dashboard", { timeout: 120_000 });

Output

Hand off to a human: https://production-sfo.browserless.io/live/?i=a1b2c3d4...

Treat that URL as a bearer secret. With interactable: true, anyone holding it controls the browser and everything logged into it, so send it over a trusted channel and keep it out of logs and tickets.

A session waiting on a human is still a session that bills, so keep the wait bounded and close in a finally. See live URLs and recording.

FAQ & Troubleshooting

The connection fails immediately with no clear error.

Check the path. A BAP endpoint ends in /bql, as in wss://production-sfo.browserless.io/chromium/bql. The CDP paths on the same host, such as /chromium, don't speak BrowserQL, and a missing token fails at connection time rather than on the first method call.

My session ended while I was still using it.

Every session has a timeout, and solving a CAPTCHA or waiting on a human eats into it. Raise it with preferences() for the session, or pass timeout on the individual call that needs longer.

Do I need one session per page?

No, but know what you're paying for. Each newPage() opens its own WebSocket and holds it until you close the browser, and each concurrent session counts against your plan limit. Reuse one page for a sequential flow, and open more only for work that genuinely runs in parallel.

Can I hand the session to Puppeteer or Playwright?

Yes. reconnect() returns browserWSEndpoint alongside the BrowserQL one, and that's a CDP endpoint you can pass straight to puppeteer.connect() or chromium.connectOverCDP(). Append your token when you connect, since the returned URLs don't include it.

Next steps

Was this page helpful?