Skip to main content

Standard Sessions

Browserless lets you keep a browser running between connections using the Browserless.reconnect CDP command. Call it before disconnecting and the browser stays alive — reconnect within the timeout window and your page state (localStorage, cookies, navigation history) is exactly where you left it.

Puppeteer only

Standard Sessions require browser.disconnect() to detach from the browser without terminating it. Playwright does not expose a disconnect() method, making this pattern unreliable with Playwright. If you're using Playwright, use Persisting State instead.

Standard Sessions Workflow

  1. Connect with Browserless

    You'll need an API key — grab one from your Browserless account. Connect using Puppeteer, open a CDP session on the page, then run your automation and set up any state you want to persist across reconnections:

    import puppeteer from "puppeteer-core";

    const TOKEN = "YOUR_API_TOKEN_HERE";
    const BROWSERLESS_URL = `wss://production-sfo.browserless.io?token=${TOKEN}`;

    async function connectToBrowserless() {
    const browser = await puppeteer.connect({
    browserWSEndpoint: BROWSERLESS_URL,
    });

    const page = (await browser.pages())[0];
    const cdp = await page.createCDPSession();

    return { browser, page, cdp };
    }

    const { browser, page, cdp } = await connectToBrowserless();

    await page.goto("https://docs.browserless.io/");
    await page.setViewport({ width: 1000, height: 800 });
    await page.screenshot({ path: "before-toggle.jpg" }); // 1. Before click

    await page.click('div.toggle_vylO.colorModeToggle_x44X > button');
    await page.screenshot({ path: "after-toggle.jpg" }); // 2. After click
  2. Prepare to reconnect

    Call Browserless.reconnect before disconnecting. This registers the browser as reconnectable for the given timeout duration (in milliseconds). After calling it, disconnect — the browser stays alive on the server.

    async function prepareReconnection(cdp, browser, timeout = 30000) {
    const { error, browserWSEndpoint } = await cdp.send("Browserless.reconnect", {
    timeout, // How long (ms) the browser stays alive waiting for a reconnect
    });

    if (error) throw new Error(error);
    console.log("Reconnection endpoint:", browserWSEndpoint);

    await browser.disconnect(); // Detaches locally; browser keeps running on server

    return browserWSEndpoint;
    }

    const browserWSEndpoint = await prepareReconnection(cdp, browser);
  3. Reconnect to the session

    Use the browserWSEndpoint from the previous step to reconnect. Append your API token to the URL:

    async function reconnectToSession(browserWSEndpoint, token) {
    const reconnectUrl = `${browserWSEndpoint}?token=${token}`;

    const browser = await puppeteer.connect({
    browserWSEndpoint: reconnectUrl,
    });

    const pages = await browser.pages();
    const page = pages[0];

    const cdp = await page.createCDPSession();

    return { browser, page, cdp };
    }

    const reconnected = await reconnectToSession(browserWSEndpoint, TOKEN);

    // Browser stayed alive — page is exactly where we left it, no re-navigation needed
    await reconnected.page.screenshot({ path: "after-reconnect.jpg" }); // 3. After reconnect
  4. Close the session

    When finished, close the browser to release resources:

    async function closeSession(browser, page) {
    await page.evaluate(() => {
    localStorage.clear();
    sessionStorage.clear();
    });

    const cookies = await page.cookies();
    await page.deleteCookie(...cookies);

    await browser.close();
    }

    await closeSession(reconnected.browser, reconnected.page);

Reconnect Response Shape

Browserless.reconnect returns a JSON object with two fields:

FieldTypeDescription
browserWSEndpointstring | nullThe URL to reconnect to. null if reconnect failed.
errorstring | nullError message if the request was rejected, otherwise null.

Always check error before using browserWSEndpoint.

Complete Example

import puppeteer from "puppeteer-core";

const TOKEN = "YOUR_API_TOKEN_HERE";
const BROWSERLESS_URL = `wss://production-sfo.browserless.io?token=${TOKEN}`;

async function main() {
console.log("Connecting to Browserless...");
const { browser, page, cdp } = await connectToBrowserless();

await page.goto("https://docs.browserless.io/");
await page.setViewport({ width: 1000, height: 800 });
await page.screenshot({ path: "before-toggle.jpg" }); // 1. Before click

await page.click('div.toggle_vylO.colorModeToggle_x44X > button');
await page.screenshot({ path: "after-toggle.jpg" }); // 2. After click

console.log("Disconnecting browser and preparing for reconnection...");
const browserWSEndpoint = await prepareReconnection(cdp, browser);

// Browser is live on the server within the TTL — reconnect from any process when ready. Here we reconnect immediately.
console.log("Reconnecting to session...");
const reconnected = await reconnectToSession(browserWSEndpoint, TOKEN);

// Browser stayed alive — page is exactly where we left it, no re-navigation needed
await reconnected.page.screenshot({ path: "after-reconnect.jpg" }); // 3. After reconnect

console.log("Closing session...");
await closeSession(reconnected.browser, reconnected.page);

console.log("Session workflow complete!");
}

main().catch(console.error);

// --- Helpers ---

async function connectToBrowserless() {
const browser = await puppeteer.connect({
browserWSEndpoint: BROWSERLESS_URL,
});
const page = (await browser.pages())[0];
const cdp = await page.createCDPSession();
return { browser, page, cdp };
}

async function prepareReconnection(cdp, browser, timeout = 30000) {
const { error, browserWSEndpoint } = await cdp.send("Browserless.reconnect", {
timeout,
});
if (error) throw new Error(error);
await browser.disconnect();
return browserWSEndpoint;
}

async function reconnectToSession(browserWSEndpoint, token) {
const browser = await puppeteer.connect({
browserWSEndpoint: `${browserWSEndpoint}?token=${token}`,
});
const pages = await browser.pages();
const page = pages[0];
const cdp = await page.createCDPSession();
return { browser, page, cdp };
}

async function closeSession(browser, page) {
await page.evaluate(() => {
localStorage.clear();
sessionStorage.clear();
});
const cookies = await page.cookies();
await page.deleteCookie(...cookies);
await browser.close();
}

Reconnection Timeout Limits

The timeout parameter passed to Browserless.reconnect controls how long the browser stays alive waiting for a reconnect. The maximum allowed value depends on your plan:

PlanMaximum Reconnection TTL
Free10 seconds (10,000ms)
Prototyping (15k–100k)30 seconds (30,000ms)
Starter (180k)60 seconds (60,000ms)
Scale (500k) and above5 minutes (300,000ms)
Enterprise (self-hosted)Custom

Passing a timeout above your plan's limit returns an immediate error:

"Reconnect timeout (Xms) exceeds the maximum allowed limit (Yms)."

Common Pitfalls

Using browser.close() instead of browser.disconnect(). browser.close() terminates the remote browser — the session is gone. You must call browser.disconnect() to detach locally while keeping the browser alive on the server.

Not appending the token to the reconnect URL. The browserWSEndpoint returned by Browserless.reconnect requires your API token: ${browserWSEndpoint}?token=${TOKEN}. Connecting without it returns a 401 Unauthorized.

Browserless.reconnect must be called before disconnecting. The CDP command must complete and return a valid browserWSEndpoint before you call browser.disconnect(). Disconnecting first means you cannot request reconnection.

Reconnecting after the TTL expires. Once the timeout elapses, the browser is cleaned up. Connecting to a stale browserWSEndpoint will fail. Always reconnect within the TTL window.

Timeout exceeds plan maximum. Always check the error field in the Browserless.reconnect response before proceeding.

Next Steps