Disconnect and reconnect to a browser
Keep a browser session alive for a short window after disconnecting and reconnect to it with all cookies, localStorage, and state intact. Ideal for scripts that need to briefly detach and resume within seconds or minutes.
Need cookies and data to survive for hours or days between runs? See Continue Browser State Across Runs for an approach that keeps browser state alive much longer.
- A Browserless API token from your account dashboard
Steps
Reconnection works in two phases: first you start a session and request a reconnection endpoint, then you reconnect using the returned URL. The browser stays alive in the background for the duration you specify.
- REST API
- Frameworks
- BQL
Use BrowserQL over HTTP to start a session, perform actions, and get a reconnection endpoint for follow-up requests.
- cURL
- JavaScript
- Python
- Java
- C#
1. Start a session and get the reconnection endpoint
Send a BQL mutation that navigates to a page and calls reconnect to keep the browser alive:
curl -X POST \
"https://production-sfo.browserless.io/stealth/bql?token=YOUR_API_TOKEN_HERE" \
-H "Content-Type: application/json" \
-d '{
"query": "mutation StartSession { goto(url: \"https://scraping-sandbox.netlify.app/aether\", waitUntil: domContentLoaded) { status } reconnect(timeout: 60000) { browserQLEndpoint browserWSEndpoint } }",
"variables": {},
"operationName": "StartSession"
}'
2. Reconnect with the returned endpoint
Use the browserQLEndpoint from the response (append your token) to send a follow-up mutation to the same browser:
curl -X POST \
"RETURNED_BROWSERQL_ENDPOINT?token=YOUR_API_TOKEN_HERE" \
-H "Content-Type: application/json" \
-d '{
"query": "mutation ContinueSession { html { html } }",
"variables": {},
"operationName": "ContinueSession"
}'
3. Check the output
The first response includes the reconnection URLs:
{
"data": {
"goto": { "status": 200 },
"reconnect": {
"browserQLEndpoint": "https://production-sfo.browserless.io/stealth/bql/abc123",
"browserWSEndpoint": "wss://production-sfo.browserless.io/chromium/abc123"
}
}
}
The second response returns the page HTML from the still-running browser.
1. Start a session and reconnect
const TOKEN = 'YOUR_API_TOKEN_HERE';
const BQL_URL = `https://production-sfo.browserless.io/stealth/bql?token=${TOKEN}`;
const startQuery = `mutation StartSession {
goto(url: "https://scraping-sandbox.netlify.app/aether", waitUntil: domContentLoaded) {
status
}
reconnect(timeout: 60000) {
browserQLEndpoint
}
}`;
const startResponse = await fetch(BQL_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: startQuery,
variables: {},
operationName: 'StartSession',
}),
});
const { data } = await startResponse.json();
const reconnectURL = `${data.reconnect.browserQLEndpoint}?token=${TOKEN}`;
const continueResponse = await fetch(reconnectURL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: 'mutation ContinueSession { html { html } }',
variables: {},
operationName: 'ContinueSession',
}),
});
const result = await continueResponse.json();
console.log(result.data.html.html.substring(0, 200));
2. Check the output
Run with node reconnect.mjs. The script navigates, disconnects, reconnects, and prints the page HTML from the still-running session.
1. Install dependencies
pip install requests
2. Start a session and reconnect
import requests
TOKEN = 'YOUR_API_TOKEN_HERE'
BQL_URL = f'https://production-sfo.browserless.io/stealth/bql?token={TOKEN}'
start_query = """
mutation StartSession {
goto(url: "https://scraping-sandbox.netlify.app/aether", waitUntil: domContentLoaded) {
status
}
reconnect(timeout: 60000) {
browserQLEndpoint
}
}
"""
start_response = requests.post(
BQL_URL,
json={
'query': start_query,
'variables': {},
'operationName': 'StartSession',
},
)
data = start_response.json()['data']
reconnect_url = f"{data['reconnect']['browserQLEndpoint']}?token={TOKEN}"
continue_response = requests.post(
reconnect_url,
json={
'query': 'mutation ContinueSession { html { html } }',
'variables': {},
'operationName': 'ContinueSession',
},
)
result = continue_response.json()
print(result['data']['html']['html'][:200])
3. Check the output
Run with python reconnect.py. The script prints the page HTML from the reconnected session.
1. Start a session and reconnect
import java.net.URI;
import java.net.http.*;
String token = "YOUR_API_TOKEN_HERE";
String bqlUrl = "https://production-sfo.browserless.io/stealth/bql?token=" + token;
String startPayload = """
{
"query": "mutation StartSession { goto(url: \\"https://scraping-sandbox.netlify.app/aether\\", waitUntil: domContentLoaded) { status } reconnect(timeout: 60000) { browserQLEndpoint } }",
"variables": {},
"operationName": "StartSession"
}
""";
HttpClient client = HttpClient.newHttpClient();
HttpRequest startRequest = HttpRequest.newBuilder()
.uri(URI.create(bqlUrl))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(startPayload))
.build();
HttpResponse<String> startResponse = client.send(startRequest, HttpResponse.BodyHandlers.ofString());
String body = startResponse.body();
// Extract browserQLEndpoint from the JSON response
String marker = "\"browserQLEndpoint\":\"";
int start = body.indexOf(marker) + marker.length();
int end = body.indexOf("\"", start);
String reconnectUrl = body.substring(start, end) + "?token=" + token;
// Send follow-up mutation to the reconnected session
String continuePayload = """
{
"query": "mutation ContinueSession { html { html } }",
"variables": {},
"operationName": "ContinueSession"
}
""";
HttpRequest continueRequest = HttpRequest.newBuilder()
.uri(URI.create(reconnectUrl))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(continuePayload))
.build();
HttpResponse<String> continueResponse = client.send(continueRequest, HttpResponse.BodyHandlers.ofString());
System.out.println(continueResponse.body());
2. Check the output
Run the class. The script starts a session, reconnects, and prints the page HTML from the still-running browser.
1. Start a session and reconnect
using System.Net.Http;
using System.Text;
using System.Text.Json;
string token = "YOUR_API_TOKEN_HERE";
string bqlUrl = $"https://production-sfo.browserless.io/stealth/bql?token={token}";
var startPayload = new
{
query = @"mutation StartSession {
goto(url: ""https://scraping-sandbox.netlify.app/aether"", waitUntil: domContentLoaded) { status }
reconnect(timeout: 60000) { browserQLEndpoint }
}",
variables = new { },
operationName = "StartSession",
};
using (HttpClient httpClient = new HttpClient())
{
var json = JsonSerializer.Serialize(startPayload);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var startResponse = await httpClient.PostAsync(bqlUrl, content);
string startBody = await startResponse.Content.ReadAsStringAsync();
var doc = JsonDocument.Parse(startBody);
string reconnectUrl = doc.RootElement
.GetProperty("data")
.GetProperty("reconnect")
.GetProperty("browserQLEndpoint")
.GetString() + $"?token={token}";
var continuePayload = new
{
query = "mutation ContinueSession { html { html } }",
variables = new { },
operationName = "ContinueSession",
};
var continueContent = new StringContent(
JsonSerializer.Serialize(continuePayload), Encoding.UTF8, "application/json");
var continueResponse = await httpClient.PostAsync(reconnectUrl, continueContent);
string continueBody = await continueResponse.Content.ReadAsStringAsync();
Console.WriteLine(continueBody);
}
2. Check the output
Run the program. The script reconnects to the still-running browser and prints the page HTML.
Use the Browserless.reconnect CDP command to keep a session alive and reconnect with Puppeteer or Playwright.
- Puppeteer
- Playwright
- BAP
1. Install dependencies
npm install puppeteer-core
2. Start a session and request reconnection
import puppeteer from 'puppeteer-core';
const TOKEN = 'YOUR_API_TOKEN_HERE';
const browser = await puppeteer.connect({
browserWSEndpoint: `wss://production-sfo.browserless.io?token=${TOKEN}`,
});
try {
const page = await browser.newPage();
const cdp = await page.createCDPSession();
await page.goto('https://scraping-sandbox.netlify.app/aether', { waitUntil: 'networkidle2' });
// Keep the browser alive for 60 seconds after disconnecting.
const { browserWSEndpoint } = await cdp.send('Browserless.reconnect', {
timeout: 60000,
});
// Disconnect (does not kill the browser — it stays alive).
await browser.close();
console.log('Reconnect URL:', browserWSEndpoint);
// Reconnect to the same browser session.
const reconnected = await puppeteer.connect({
browserWSEndpoint: `${browserWSEndpoint}?token=${TOKEN}`,
});
const pages = await reconnected.pages();
const existingPage = pages[0];
console.log('Still on:', await existingPage.title());
await reconnected.close();
} catch (err) {
await browser.close();
throw err;
}
3. Check the output
Run with node reconnect.mjs. The script connects, navigates, disconnects, reconnects, and confirms the page title is preserved.
1. Install dependencies
npm install playwright-core
2. Start a session and request reconnection
import { chromium } from 'playwright-core';
const TOKEN = 'YOUR_API_TOKEN_HERE';
const browser = await chromium.connect(
`wss://production-sfo.browserless.io/chromium/playwright?token=${TOKEN}`
);
try {
const page = await browser.newPage();
const cdp = await page.context().newCDPSession(page);
await page.goto('https://scraping-sandbox.netlify.app/aether', { waitUntil: 'networkidle' });
// Keep the browser alive for 60 seconds after disconnecting.
const { browserWSEndpoint } = await cdp.send('Browserless.reconnect', {
timeout: 60000,
});
// Disconnect (does not kill the browser — it stays alive).
await browser.close();
console.log('Reconnect URL:', browserWSEndpoint);
// Reconnect to the same browser session.
const reconnected = await chromium.connect(
`${browserWSEndpoint}?token=${TOKEN}`
);
const pages = reconnected.contexts()[0]?.pages() ?? [];
if (pages.length > 0) {
console.log('Still on:', await pages[0].title());
}
await reconnected.close();
} catch (err) {
await browser.close();
throw err;
}
3. Check the output
Run with node reconnect.mjs. The script connects, navigates, disconnects, reconnects, and confirms the page title is preserved.
1. Install dependencies
npm install @browserless.io/bap-ts
2. Connect and hand off the session
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 {
const page = await browser.newPage();
await page.goto('https://scraping-sandbox.netlify.app/aether', { waitUntil: 'domContentLoaded' });
const { browserQLEndpoint, browserWSEndpoint } = await page.reconnect({ timeout: 60000 });
// Endpoints come back without credentials, so hand them on as-is and let
// the next client add its own token. Never log a token-bearing URL.
console.log({ browserQLEndpoint, browserWSEndpoint });
// No close() here. The session has to outlive this script for the next
// client to reach it, and the reconnect timeout bounds how long it lives.
} catch (error) {
// Only close when the handoff never happened, so nothing is left stranded.
await browser.close();
throw error;
}
3. Reconnect from the next client
reconnect() returns endpoints without the token, so every follow-up connection has to add its own or it gets a 401 Unauthorized:
// More BAP work on the same session.
const resumed = Browserless.connect({
browserWSEndpoint: browserQLEndpoint,
token: TOKEN,
});
// …or hand the page to Puppeteer over CDP.
const puppeteerBrowser = await puppeteer.connect({
browserWSEndpoint: `${browserWSEndpoint}?token=${TOKEN}`,
});
4. Check the output
Both clients attach to the same browser, so cookies, localStorage, and page state carry over. The session stays alive until the timeout expires or the next client closes it.
1. Write the first mutation
Navigate to a page and call reconnect to keep the browser alive. The response includes a browserQLEndpoint for follow-up requests and a browserWSEndpoint for framework connections:
reconnect(timeout: 60000)requires at least a Starter plan. The Free plan caps reconnect timeouts at 10 seconds. Scale plans support up to 5 minutes.
mutation StartSession {
goto(url: "https://scraping-sandbox.netlify.app/aether", waitUntil: domContentLoaded) {
status
}
reconnect(timeout: 60000) {
browserQLEndpoint
browserWSEndpoint
}
}
2. Send a follow-up mutation to the same browser
Use the browserQLEndpoint from the response (append ?token=YOUR_API_TOKEN_HERE) and POST a new mutation to it:
mutation ContinueSession {
html {
html
}
}
3. Run it
Paste the first mutation into the BQL IDE and click Run. Copy the browserQLEndpoint from the response, open a new BQL IDE tab pointed at that endpoint, and run the second mutation.
4. Check the output
The first response includes the reconnection URLs:
{
"data": {
"goto": { "status": 200 },
"reconnect": {
"browserQLEndpoint": "https://production-sfo.browserless.io/stealth/bql/abc123",
"browserWSEndpoint": "wss://production-sfo.browserless.io/chromium/abc123"
}
}
}
The second response returns data from the same browser. All cookies, localStorage, and page state are preserved.