Skip to main content

Persisting State

The Session REST API creates long-lived browser sessions that maintain cookies, localStorage, and other browser state across multiple BQL query runs. State persists even after you disconnect and reconnect to the same session. Depending on your plan, session data can persist for up to 90 days.

Comparison with BQL Reconnect

The Session API and the BQL reconnect mutation both maintain browser continuity, but they work differently.

FeatureSession API + BQLBQL Reconnect
Session CreationExplicit via REST APIImplicit with first query
Lifecycle ControlProgrammatic start/stopTimeout-based only
State ManagementPersistent across disconnectionsRequires active connection
Proxy ConfigurationSet once, applies to all queriesPer-query configuration
Stealth RequirementRequired for BQL supportAvailable for all BQL queries

Persisting State Workflow

  1. Create a Session

    POST to /session with stealth: true. Save the returned object — it contains the URLs you need for BQL queries and session cleanup.

    Stealth Sessions Required

    browserQL is returned for all sessions, but BQL queries return a 400 error unless the session was created with stealth: true.

    const response = await fetch(
    "https://production-sfo.browserless.io/session?token=YOUR_API_TOKEN_HERE",
    {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
    ttl: 300000,
    stealth: true,
    }),
    }
    );

    if (!response.ok) {
    throw new Error(`Failed to create session: ${response.status}`);
    }

    const session = await response.json();
    console.log("Session created:", session.id);
  2. Run BQL Queries

    POST BQL mutations to session.browserQL. This is a fully-qualified URL returned for all sessions that inherits all session properties (proxy, stealth mode, etc.). BQL queries against it require the session to have been created with stealth: true.

    const query = `
    mutation SetDarkMode {
    goto(url: "https://docs.browserless.io/", waitUntil: networkIdle) {
    status
    }
    click(selector: "div.toggle_vylO.colorModeToggle_x44X > button") {
    time
    }
    }
    `;

    const bqlResponse = await fetch(session.browserQL, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ query }),
    });

    const result = await bqlResponse.json();
    console.log("Dark mode toggled:", result.data);
  3. Stop the Session

    You can POST to session.browserQL as many times as needed across multiple script runs — state persists across all of them. Only send a DELETE to session.stop when you want to permanently discard the session data. Sessions also expire automatically when their TTL elapses.

    const stopResponse = await fetch(session.stop, { method: "DELETE" });

    if (stopResponse.ok) {
    console.log("Session stopped.");
    }

Complete Example

This example creates a session, toggles dark mode in one BQL query, then verifies the theme preference persists in a second query against the same session.

async function main() {
// 1. Create a stealth session
const response = await fetch(
"https://production-sfo.browserless.io/session?token=YOUR_API_TOKEN_HERE",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ttl: 300000, stealth: true }),
}
);

if (!response.ok) {
throw new Error(`Failed to create session: ${response.status}`);
}

const session = await response.json();
console.log("Session created:", session.id);

// 2. First query: navigate and toggle dark mode
const toggleQuery = `
mutation SetDarkMode {
goto(url: "https://docs.browserless.io/", waitUntil: networkIdle) {
status
}
click(selector: "div.toggle_vylO.colorModeToggle_x44X > button") {
time
}
}
`;

const toggleResponse = await fetch(session.browserQL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: toggleQuery }),
});

const toggleResult = await toggleResponse.json();
console.log("Dark mode toggled:", toggleResult.data.click.time);

// 3. Second query: verify theme persists (reuse same session.browserQL URL)
const verifyQuery = `
mutation VerifyTheme {
goto(url: "https://docs.browserless.io/", waitUntil: networkIdle) {
status
}
theme: evaluate(content: "return localStorage.getItem('theme');") {
value
}
}
`;

const verifyResponse = await fetch(session.browserQL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: verifyQuery }),
});

const verifyResult = await verifyResponse.json();
console.log("Theme persisted:", verifyResult.data.theme.value);

// 4. Clean up
await fetch(session.stop, { method: "DELETE" });
console.log("Session stopped.");
}

main().catch(console.error);

Session Response Schema

A successful POST /session returns a JSON object with these fields:

PropertyTypeDescription
idstringUnique session identifier
connectstringWebSocket URL for CDP-based libraries (Puppeteer, Playwright). Token is pre-embedded.
browserQLstringURL for running BQL queries against this session. Returned for all sessions. Requires stealth: true to use. Token is pre-embedded.
stopstringURL for session termination via DELETE request. Token is pre-embedded.
ttlnumberSession time-to-live in milliseconds

Persisted Session Data Duration

Session data (cookies, localStorage, cache) persists for the duration of the session TTL, up to the plan maximum:

PlanMaximum Session Lifetime
Free1 day
Prototyping7 days
Starter30 days
Scale90 days
EnterpriseCustom

You can also limit persistence duration by passing a lower ttl value to the session API.

Session Configuration Options

All session configuration options apply to both WebSocket connections and BQL queries:

ParameterTypeDefaultDescription
ttlnumber300000Time-to-live in milliseconds. Max varies by plan.
stealthbooleanfalseRequired for BQL support. Enables stealth mode.
headlessbooleantrueRun browser in headless mode.
browserstring'chromium'Browser type: 'chromium' or 'chrome'.
blockAdsbooleanfalseEnable ad blocking.
argsstring[][]Additional Chrome launch arguments.
proxyobjectnullProxy configuration. When set, applies to all BQL queries and WebSocket connections for this session. See proxy docs for the full configuration shape.

Best Practices

  1. Store session URLs together. Save session.id, session.browserQL, and session.stop as a unit after session creation. You need all three for queries, cleanup, and debugging.

  2. Delete sessions when done. Send a DELETE to session.stop when your workflow completes. Relying on TTL expiry alone wastes resources and can hit session limits.

  3. Handle expired sessions. If a POST to session.browserQL returns a non-2xx status, the session has likely expired or been deleted. Create a new session and retry.

Next Steps