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.
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
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 clickPrepare to reconnect
Call
Browserless.reconnectbefore disconnecting. This registers the browser as reconnectable for the giventimeoutduration (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);Reconnect to the session
Use the
browserWSEndpointfrom 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 reconnectClose 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:
| Field | Type | Description |
|---|---|---|
browserWSEndpoint | string | null | The URL to reconnect to. null if reconnect failed. |
error | string | null | Error 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:
| Plan | Maximum Reconnection TTL |
|---|---|
| Free | 10 seconds (10,000ms) |
| Prototyping (15k–100k) | 30 seconds (30,000ms) |
| Starter (180k) | 60 seconds (60,000ms) |
| Scale (500k) and above | 5 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.