How to Create a Puppeteer Connection from a Session
Attach Puppeteer to an existing Chrome session, find its browser WebSocket endpoint, and choose whether to disconnect or close the browser.
Use puppeteer.connect() to attach Puppeteer to a Chrome browser that is already running and exposes a remote debugging endpoint. Pass the browser’s WebSocket endpoint as browserWSEndpoint, or use browserURL to let Puppeteer discover the endpoint. Call browser.disconnect() when Puppeteer should detach while Chrome stays open; call browser.close() when the browser should shut down.
Connect to a Puppeteer-launched browser
If your application launched the browser with Puppeteer, save browser.wsEndpoint() before disconnecting. You can pass that endpoint to a later Puppeteer process or another part of your application.
const puppeteer = require('puppeteer');
async function main() {
const launched = await puppeteer.launch({ headless: false });
const browserWSEndpoint = launched.wsEndpoint();
console.log('Browser endpoint:', browserWSEndpoint);
await launched.disconnect();
// Later, attach to the same still-running browser.
const browser = await puppeteer.connect({ browserWSEndpoint });
const pages = await browser.pages();
const page = pages[0] ?? await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
console.log(await page.title());
// Leaves Chrome running and its pages open.
await browser.disconnect();
}
main().catch(error => {
console.error(error);
process.exitCode = 1;
});
Run it in a Node.js project with Puppeteer installed, for example with npm install puppeteer. The endpoint only works while the browser process that created it is still running.
Connect to an independently started Chrome
Start Chrome with remote debugging enabled, then connect using the debugging port. Chrome’s debugging endpoint grants control over that browser session, so keep it reachable only by trusted local processes or clients.
google-chrome --remote-debugging-port=9222 --user-data-dir=/tmp/puppeteer-session
On macOS, a typical launch command is:
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 --user-data-dir=/tmp/puppeteer-session
Use a dedicated profile directory for automation. If Chrome is already running with the same profile, it may reuse that process or reject the debugging setup; close it first or choose a separate profile. The precise executable path depends on the operating system and installation.
Option A: Let Puppeteer discover the endpoint
Pass the browser’s HTTP debugging address as browserURL. Puppeteer discovers the browser WebSocket endpoint from the debugging service.
const puppeteer = require('puppeteer');
async function main() {
const browser = await puppeteer.connect({ browserURL: 'http://127.0.0.1:9222' });
try {
const pages = await browser.pages();
const page = pages[0] ?? await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
console.log(await page.title());
} finally {
await browser.disconnect();
}
}
main().catch(console.error);
Option B: Read and pass the WebSocket endpoint
Request http://127.0.0.1:9222/json/version and copy its webSocketDebuggerUrl value. Pass that full browser-level URL to browserWSEndpoint. Do not substitute a page target WebSocket URL.
const puppeteer = require('puppeteer');
async function main() {
const response = await fetch('http://127.0.0.1:9222/json/version');
if (!response.ok) throw new Error(`Debug endpoint returned ${response.status}`);
const info = await response.json();
if (!info.webSocketDebuggerUrl) throw new Error('No webSocketDebuggerUrl in /json/version');
const browser = await puppeteer.connect({ browserWSEndpoint: info.webSocketDebuggerUrl });
try {
const page = (await browser.pages())[0] ?? await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
console.log(await page.title());
} finally {
await browser.disconnect();
}
}
main().catch(console.error);
The browser endpoint commonly has the form ws://HOST:PORT/devtools/browser/<id>. Use the actual value returned by Chrome; the identifier is specific to the running browser.
Choose the right Puppeteer connection option
| Option | Use it for | Example |
|---|---|---|
browserWSEndpoint |
You already have the browser WebSocket URL, from browser.wsEndpoint() or /json/version. |
puppeteer.connect({ browserWSEndpoint: wsUrl }) |
browserURL |
You know the HTTP debugging host and port and want Puppeteer to discover the WebSocket URL. | puppeteer.connect({ browserURL: 'http://127.0.0.1:9222' }) |
defaultViewport |
You need to set the viewport when connecting. Consult the current ConnectOptions documentation for supported values and behavior. | puppeteer.connect({ browserURL, defaultViewport: { width: 1280, height: 800 } }) |
protocolTimeout |
You need to control how long Puppeteer waits for a protocol call. Check the current API reference for the version-specific default and units. | puppeteer.connect({ browserURL, protocolTimeout: 30000 }) |
Use one connection source: browserWSEndpoint when you have the WebSocket URL, or browserURL for discovery. Puppeteer’s supported connection settings can vary by version; see the ConnectOptions API reference and browser management guide for the installed version.
Find the debugging endpoint
- Start Chrome with a remote debugging port, such as
--remote-debugging-port=9222. - From the same machine, open or request
http://127.0.0.1:9222/json/version. - Copy the
webSocketDebuggerUrlfield and pass it tobrowserWSEndpoint, or give Puppeteer the base debugging address viabrowserURL.
If Chrome starts with --remote-debugging-port=0, it selects an available port. Chrome writes connection details, including the port, to the DevToolsActivePort file in the browser profile directory. The browser WebSocket URL is also available from Chrome’s debugging discovery response. See the Chrome DevTools Protocol documentation and Chrome remote debugging guide.
Disconnect Puppeteer or close Chrome
| Call | Effect | Use when |
|---|---|---|
await browser.disconnect() |
Detaches this Puppeteer client. Chrome and its pages remain open. | A person, another client, or a later automation step should keep using the session. |
await browser.close() |
Closes the browser. | This automation owns the browser lifecycle and is finished with it. |
Disconnecting one client does not transfer or end another client’s connection. Be explicit about lifecycle ownership when several processes can connect to the same browser. Puppeteer’s browser management guide documents the distinction.
Session security and operational limits
An attached browser can contain signed-in accounts, cookies, and other active session data. Chrome warns that an agent connected to an existing session inherits access to that data. Treat the debugging endpoint like a credential:
- Bind debugging to loopback when the client runs on the same host; do not expose the port to an untrusted network.
- Use a dedicated browser profile for automation when practical, rather than a person’s everyday profile.
- Restrict access through the host and network controls you already use, and stop the debugging-enabled browser when it is no longer needed.
- Do not log or publish WebSocket URLs. A holder may be able to control the associated browser session.
Chrome’s remote debugging guidance explains the access inherited by an agent attached to an existing session.
Reliability, performance, and cost
puppeteer.connect() attaches to a running browser; it does not make that browser process durable. If Chrome exits, the connection ends and the endpoint becomes invalid. A supervising process should own browser startup, detect disconnects, and reconnect only after confirming that the browser is available again.
- Reuse: Reusing a browser avoids repeated startup, but manage pages and state deliberately so one task does not interfere with another.
- Timeouts: A successful connection does not guarantee a page will load. Set navigation waits appropriate to the site and handle navigation and protocol errors.
- Capacity: Keep the number of concurrent pages and tasks within the memory and CPU capacity of the browser host. The dossier provides no universal throughput figure.
- Cost: Puppeteer is an open-source library; this workflow has no per-screenshot API charge by itself. You still pay for the machine or service running Chrome and for any other infrastructure you use. No benchmark or fixed operating cost applies to every deployment.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Connection refused at port 9222 | Chrome is not running with remote debugging enabled, is listening on another port, or is in another container or host. | Start Chrome with the debugging flag, confirm its host and port, and test /json/version from the same network namespace as Node.js. |
browserWSEndpoint fails to connect |
The URL is stale, malformed, or belongs to a page target rather than the browser. | Fetch a fresh webSocketDebuggerUrl from /json/version or use the latest browser.wsEndpoint() value. |
| Chrome starts but the debugging endpoint is missing | The flag was not applied to the process you are trying to attach to, or an existing Chrome process/profile was reused. | Close the relevant process, relaunch with the flag and a dedicated --user-data-dir, then check /json/version. |
| The browser closes after automation | The code called browser.close() or another owner shut down Chrome. |
Use browser.disconnect() for a client detach, and ensure the process that owns Chrome remains alive. |
| Connection drops during work | Chrome crashed, was stopped, or became unreachable over the network. | Handle the disconnect, inspect browser-process logs and host capacity, then reconnect using a newly discovered endpoint. |
| A new page is blank or navigation times out | Connection succeeded, but the target site is slow, blocked, or still loading resources. | Check the URL and browser page state; choose a navigation wait condition that matches the task and handle timeout errors separately from connection errors. |
Or skip the browser setup
If your goal is a screenshot rather than controlling an existing Chrome session, ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request returns an image or PDF. See the API documentation for options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
- Cookie banners and consent prompts, newsletter popups, and chat widgets are removed before capture; each cleanup step can be turned off.
- Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed. Response headers report the page verdict and billing status.
- An MCP server provides
take_screenshot,get_page_info, andcapture_pdffor AI agents and MCP clients. - The Free plan includes 1,000 shots a month with no card. Paid plans start at $5 for 3,000 shots; every feature is on every plan.
Sign up for ScreenshotNeo free: 1,000 screenshots a month, no card required.
FAQ
Can I attach Puppeteer to Chrome I opened manually?
Yes, if that Chrome instance was started with remote debugging enabled and you can reach its debugging endpoint.
Can I use a page’s WebSocket URL?
For browserWSEndpoint, use the browser endpoint from /json/version, not an individual page target endpoint.
Does disconnecting clear cookies or sign out?
No. disconnect() detaches Puppeteer and leaves the browser session open, including its existing pages and session data.


