Continue Browser State Across Runs
Create a long-lived browser session via the Session API, store data like cookies and localStorage, disconnect, then reconnect later and verify state continues across runs.
Need to briefly pause your script and resume the same browser within seconds or minutes? See Disconnect and Reconnect to a Browser for a simpler approach that doesn't require managing sessions via API.
- A Browserless API token from your account dashboard
Steps
The Session API gives you full REST control over session lifecycle. Create a session with a TTL, connect and set state, disconnect safely, then reconnect and verify your data is still there.
- REST API
- Frameworks
- BQL
Use the Session API REST endpoints to create, connect to, and manage long-lived browser sessions.
- cURL
- JavaScript
- Python
- Java
- C#
1. Create a session
curl -X POST "https://production-sfo.browserless.io/session?token=YOUR_API_TOKEN_HERE" \
-H "Content-Type: application/json" \
-d '{
"ttl": 300000,
"stealth": true,
"headless": false
}'
The response returns connect (WebSocket URL) and stop (DELETE URL) fields.
2. Stop the session when done
curl -X DELETE "STOP_URL_FROM_STEP_1&force=true"
1. Create the session and set state
const TOKEN = "YOUR_API_TOKEN_HERE";
const sessionResponse = await fetch(
`https://production-sfo.browserless.io/session?token=${TOKEN}`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ttl: 300000, stealth: true, headless: false }),
}
);
const session = await sessionResponse.json();
console.log("Session created:", session.id);
2. Connect via Puppeteer and store data
import puppeteer from "puppeteer-core";
const browser = await puppeteer.connect({ browserWSEndpoint: session.connect });
const page = await browser.newPage();
await page.goto("https://automationexercise.com", { waitUntil: "networkidle2" });
await page.evaluate(() => {
localStorage.setItem(
"shoppingCart",
JSON.stringify({ items: [{ id: 1 }], totalItems: 1, totalPrice: 500 })
);
localStorage.setItem("userPreferences", JSON.stringify({ theme: "dark" }));
});
await browser.disconnect();
3. Reconnect and verify state persists
const browser2 = await puppeteer.connect({ browserWSEndpoint: session.connect });
const page2 = await browser2.newPage();
await page2.goto("https://automationexercise.com", { waitUntil: "networkidle2" });
const cart = await page2.evaluate(() => localStorage.getItem("shoppingCart"));
console.log("Cart persisted:", JSON.parse(cart));
await browser2.close();
4. Delete the session
await fetch(`${session.stop}&force=true`, { method: "DELETE" });
console.log("Session stopped.");
1. Install dependencies
pip install requests
2. Create the session
import requests
TOKEN = "YOUR_API_TOKEN_HERE"
session_response = requests.post(
f"https://production-sfo.browserless.io/session?token={TOKEN}",
json={"ttl": 300000, "stealth": True, "headless": False},
)
session = session_response.json()
print("Session created:", session["id"])
print("Connect URL:", session["connect"])
print("Stop URL:", session["stop"])
3. Connect and verify with Playwright
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp(session["connect"])
context = browser.contexts[0] if browser.contexts else browser.new_context()
page = context.new_page()
page.goto("https://automationexercise.com")
page.evaluate("""() => {
localStorage.setItem('shoppingCart', JSON.stringify({items: [{id: 1}]}));
}""")
browser.close()
# Reconnect and verify
browser2 = p.chromium.connect_over_cdp(session["connect"])
context2 = browser2.contexts[0] if browser2.contexts else browser2.new_context()
page2 = context2.new_page()
page2.goto("https://automationexercise.com")
cart = page2.evaluate("() => localStorage.getItem('shoppingCart')")
print("Cart persisted:", cart)
browser2.close()
4. Stop the session
requests.delete(f"{session['stop']}&force=true")
print("Session stopped.")
1. Create the session
import java.net.URI;
import java.net.http.*;
String token = "YOUR_API_TOKEN_HERE";
String endpoint = "https://production-sfo.browserless.io/session?token=" + token;
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(endpoint))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(
"{\"ttl\": 300000, \"stealth\": true, \"headless\": false}"
))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
String body = response.body();
System.out.println("Session created: " + body);
// Parse connect and stop URLs from response
int connectStart = body.indexOf("\"connect\":\"") + 11;
int connectEnd = body.indexOf("\"", connectStart);
String connectUrl = body.substring(connectStart, connectEnd);
int stopStart = body.indexOf("\"stop\":\"") + 8;
int stopEnd = body.indexOf("\"", stopStart);
String stopUrl = body.substring(stopStart, stopEnd);
2. Stop the session when done
HttpRequest deleteRequest = HttpRequest.newBuilder()
.uri(URI.create(stopUrl + "&force=true"))
.DELETE()
.build();
client.send(deleteRequest, HttpResponse.BodyHandlers.ofString());
System.out.println("Session stopped.");
1. Create the session
using System.Net.Http;
using System.Text;
using System.Text.Json;
var token = "YOUR_API_TOKEN_HERE";
var client = new HttpClient();
var content = new StringContent(
JsonSerializer.Serialize(new { ttl = 300000, stealth = true, headless = false }),
Encoding.UTF8,
"application/json"
);
var response = await client.PostAsync(
$"https://production-sfo.browserless.io/session?token={token}",
content
);
var body = await response.Content.ReadAsStringAsync();
var session = JsonSerializer.Deserialize<JsonElement>(body);
var connectUrl = session.GetProperty("connect").GetString();
var stopUrl = session.GetProperty("stop").GetString();
Console.WriteLine($"Session created: {session.GetProperty("id")}");
2. Stop the session when done
await client.DeleteAsync($"{stopUrl}&force=true");
Console.WriteLine("Session stopped.");
Connect to a Session API session with Puppeteer or Playwright, store browser state, disconnect, then reconnect and verify persistence.
- Puppeteer
- Playwright
1. Install dependencies
npm install puppeteer-core
2. Create a session and connect
import puppeteer from "puppeteer-core";
const TOKEN = "YOUR_API_TOKEN_HERE";
// Create a long-lived session
const sessionResponse = await fetch(
`https://production-sfo.browserless.io/session?token=${TOKEN}`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ttl: 300000, stealth: true }),
}
);
const session = await sessionResponse.json();
// Connect and set browser state
const browser = await puppeteer.connect({ browserWSEndpoint: session.connect });
try {
const page = await browser.newPage();
await page.goto("https://automationexercise.com", { waitUntil: "networkidle2" });
await page.evaluate(() => {
localStorage.setItem(
"shoppingCart",
JSON.stringify({ items: [{ id: 1, name: "Blue Top" }], totalItems: 1, totalPrice: 500 })
);
localStorage.setItem("userPreferences", JSON.stringify({ theme: "dark", language: "en" }));
});
console.log("State set. Disconnecting...");
} finally {
await browser.disconnect();
}
3. Reconnect and verify state
// Reconnect to the same session. State persists across disconnects.
const browser2 = await puppeteer.connect({ browserWSEndpoint: session.connect });
try {
const page2 = await browser2.newPage();
await page2.goto("https://automationexercise.com", { waitUntil: "networkidle2" });
const cart = await page2.evaluate(() => localStorage.getItem("shoppingCart"));
const prefs = await page2.evaluate(() => localStorage.getItem("userPreferences"));
console.log("Cart:", JSON.parse(cart));
console.log("Preferences:", JSON.parse(prefs));
} finally {
await browser2.close();
}
// Clean up
await fetch(`${session.stop}&force=true`, { method: "DELETE" });
1. Install dependencies
npm install playwright-core
2. Create a session and connect
import { chromium } from "playwright-core";
const TOKEN = "YOUR_API_TOKEN_HERE";
// Create a long-lived session
const sessionResponse = await fetch(
`https://production-sfo.browserless.io/session?token=${TOKEN}`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ttl: 300000, stealth: true }),
}
);
const session = await sessionResponse.json();
// Connect and set browser state
const browser = await chromium.connectOverCDP(session.connect);
try {
const context = browser.contexts()[0] ?? await browser.newContext();
const page = await context.newPage();
await page.goto("https://automationexercise.com", { waitUntil: "networkidle" });
await page.evaluate(() => {
localStorage.setItem(
"shoppingCart",
JSON.stringify({ items: [{ id: 1, name: "Blue Top" }], totalItems: 1, totalPrice: 500 })
);
localStorage.setItem("userPreferences", JSON.stringify({ theme: "dark", language: "en" }));
});
console.log("State set. Closing browser...");
} finally {
await browser.close();
}
3. Reconnect and verify state
// Reconnect — a new browser process starts but loads persisted data from disk
const browser2 = await chromium.connectOverCDP(session.connect);
try {
const context2 = browser2.contexts()[0] ?? await browser2.newContext();
const page2 = await context2.newPage();
await page2.goto("https://automationexercise.com", { waitUntil: "networkidle" });
const cart = await page2.evaluate(() => localStorage.getItem("shoppingCart"));
const prefs = await page2.evaluate(() => localStorage.getItem("userPreferences"));
console.log("Cart:", JSON.parse(cart));
console.log("Preferences:", JSON.parse(prefs));
} finally {
await browser2.close();
}
// Clean up
await fetch(`${session.stop}&force=true`, { method: "DELETE" });
The Session API returns a browserQL endpoint for running BQL queries against a persistent session. State set in one query is available in subsequent queries against the same session.
Sessions must be created with stealth: true for the browserQL endpoint to accept queries.
1. Create a session
curl -X POST "https://production-sfo.browserless.io/session?token=YOUR_API_TOKEN_HERE" \
-H "Content-Type: application/json" \
-d '{"ttl": 300000, "stealth": true}'
Save the browserQL field from the response. This is your BQL endpoint for this session.
2. Write a mutation that sets state
mutation SetState {
goto(url: "https://automationexercise.com", waitUntil: networkIdle) {
status
}
evaluate(
expression: """
localStorage.setItem('shoppingCart', JSON.stringify({items: [{id: 1}], totalItems: 1}));
localStorage.setItem('userPreferences', JSON.stringify({theme: 'dark'}));
'state set'
"""
) {
value
}
}
3. Run it against the session
Paste into the BQL IDE and click Run.
4. Run a follow-up query to verify state
Send another query to the same browserQL endpoint. The localStorage from the first query is still available:
mutation VerifyState {
goto(url: "https://automationexercise.com", waitUntil: networkIdle) {
status
}
evaluate(
expression: "localStorage.getItem('shoppingCart')"
) {
value
}
}
5. Check the output
{
"data": {
"goto": { "status": 200 },
"evaluate": { "value": "{\"items\":[{\"id\":1}],\"totalItems\":1}" }
}
}
State from the first query persists across subsequent BQL calls against the same session.
Next steps
- Reconnect to a Browser — lightweight mid-script reconnection for short-lived sessions
- Log In and Reuse Sessions — save login state to a named profile for reuse
- Save Logins to Authenticated Profiles — capture and replay authenticated browser state