How to Connect Puppeteer to a Browser in Node.js
Connect Puppeteer to an existing browser with a WebSocket endpoint or browser URL, then manage the session and browser lifecycle safely.
Use puppeteer.connect() to attach Node.js Puppeteer to a browser that is already running. Pass either its DevTools WebSocket endpoint as browserWSEndpoint or a reachable browser URL as browserURL. Puppeteer returns a Browser instance you can use to open pages and interact with the browser. Call browser.disconnect() to detach while leaving the browser running, or browser.close() to shut it down.
This guide covers endpoint discovery, runnable Node.js examples, lifecycle choices, troubleshooting, and operational considerations. For the exact current option definitions, see the Puppeteer connect API and ConnectOptions reference.
1. Check that the browser is reachable
The browser must already be running, and the Node.js process must be able to reach the endpoint it exposes. A local browser and a browser hosted in another environment follow the same basic flow, but the host, port, scheme, and any authentication requirements come from that environment.
When the browser exposes the Chrome DevTools Protocol (CDP) HTTP endpoint, request http://HOST:PORT/json/version. The JSON response includes webSocketDebuggerUrl; use the actual value returned by the browser host. Do not copy an example endpoint from documentation as if it were universal. See Puppeteer’s Browser.wsEndpoint() reference and browser management guide.
curl http://127.0.0.1:9222/json/version
If the host provides a WebSocket endpoint directly, use that instead. Treat the endpoint as sensitive if it grants control of a browser session: keep it out of public code, logs, and error messages.
2. Install Puppeteer and connect
For a project that already uses ES modules, install Puppeteer and set the endpoint in the environment. This example connects, opens a page, prints its title, and detaches cleanly. The browser itself remains running.
npm install puppeteer
# Set this to the endpoint supplied by your browser process or host.
export BROWSER_WS_ENDPOINT='ws://127.0.0.1:9222/devtools/browser/REPLACE_WITH_REAL_ID'
cat > connect.mjs <<'EOF'
import puppeteer from 'puppeteer';
const endpoint = process.env.BROWSER_WS_ENDPOINT;
if (!endpoint) {
throw new Error('Set BROWSER_WS_ENDPOINT to the browser WebSocket endpoint.');
}
const browser = await puppeteer.connect({
browserWSEndpoint: endpoint,
});
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
console.log(await page.title());
} finally {
browser.disconnect();
}
EOF
node connect.mjs
Replace the sample endpoint with the real endpoint from the browser process or hosting environment. The placeholder browser ID is not a valid endpoint. The browserWSEndpoint option expects the WebSocket URL, not the /json/version HTTP URL.
3. Choose the connection option
| Option | Use it when | What to provide |
|---|---|---|
browserWSEndpoint |
You have the browser’s DevTools WebSocket URL. | The full WebSocket endpoint, often found in webSocketDebuggerUrl. |
browserURL |
You have the browser’s reachable DevTools HTTP address. | The browser URL supplied by the host, such as its origin and debugging port. |
wsOptions |
You need to configure Node.js WebSocket connection behavior. | WebSocket options supported by the installed Puppeteer version; use wsOptions.headers for headers. |
Use one endpoint form that matches what your browser host supplies. For a URL-based connection:
import puppeteer from 'puppeteer';
const browser = await puppeteer.connect({
browserURL: process.env.BROWSER_DEBUG_URL,
});
try {
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
} finally {
browser.disconnect();
}
Set BROWSER_DEBUG_URL to the reachable DevTools browser URL, not to the target website you want to visit. The headers option on its own is deprecated in the current options reference; configure connection headers through wsOptions.headers when the host requires them. Follow the reference for the Puppeteer version installed in your project.
4. Use the connected browser
The returned object is a Puppeteer Browser. You can create pages and use Puppeteer’s page APIs as you normally would. For example, this captures a page screenshot and writes a local file:
import puppeteer from 'puppeteer';
import { writeFile } from 'node:fs/promises';
const browser = await puppeteer.connect({
browserWSEndpoint: process.env.BROWSER_WS_ENDPOINT,
});
try {
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 1000 });
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
const image = await page.screenshot({ type: 'png', fullPage: true });
await writeFile('page.png', image);
} finally {
browser.disconnect();
}
Page navigation and screenshot options belong to Puppeteer’s page API. Pick a navigation wait condition that fits the site: waiting for all network activity to settle can take longer or never settle on pages with persistent connections. A screenshot also depends on the page loading successfully and on the browser’s current state.
5. Decide who owns browser shutdown
The cleanup call is an ownership decision, not just a style preference.
browser.disconnect()detaches Puppeteer. It does not close the browser or its pages, so another process or user can keep using them.browser.close()gracefully closes the browser. Use it when this code owns the browser lifecycle and the browser should stop after the work.
For a browser managed by a separate service, worker, or person, disconnect when your task finishes. For a browser your application launched and is responsible for shutting down, close it when appropriate. Avoid closing a shared browser just because one task has completed.
6. Compatibility and security considerations
Browser compatibility depends on the installed Puppeteer release. Check the row for that release in Puppeteer’s supported browsers table; do not treat a version pairing in an older guide as evergreen. Puppeteer documents that, starting with v20.0.0, it downloads and works with Chrome for Testing, and that Firefox support moved to stable Firefox starting with v23.0.0.
Puppeteer defaults to CDP when connecting, according to the current ConnectOptions reference. That reference also describes an experimental Chrome-only allowlist option requiring Chrome 149 or newer. The allowlist limits matching browser network requests while Puppeteer is attached, but Puppeteer documents it as an additional guardrail rather than complete network sandboxing. Use operating-system or container controls when full isolation is required.
For a remote browser, confirm that the endpoint is reachable from the Node.js runtime, authentication is configured as the host requires, and the endpoint is protected from unintended access. A working WebSocket connection does not by itself establish that the browser is isolated from other processes or networks.
7. Troubleshoot common connection problems
| Symptom | Likely cause | Fix |
|---|---|---|
| Connection refused or timeout | The browser is stopped, the host or port is wrong, or the Node.js runtime cannot reach it. | Check the host-supplied endpoint, browser process status, port exposure, and network path from the same environment running Node.js. |
| WebSocket handshake or HTTP error | An HTTP discovery URL was passed as a WebSocket endpoint, or the host requires authentication or headers. | Read webSocketDebuggerUrl from /json/version for browserWSEndpoint, or use the documented browserURL option. Configure required headers through wsOptions.headers as documented for your version. |
| Invalid endpoint or malformed URL | The endpoint is incomplete, contains a placeholder, or has the wrong scheme or path. | Copy the full endpoint supplied by the running browser or host. Do not guess the browser ID or substitute the target page URL. |
| Browser version or protocol errors | The remote browser does not match the compatibility expectations of the installed Puppeteer version. | Check Puppeteer’s supported-browser table for the installed release and use a compatible browser version. |
| The browser closes after the script exits | The script or host owns shutdown, or code called browser.close(). |
Use browser.disconnect() for a browser that should remain available, and confirm the hosting environment does not stop it independently. |
| The browser stays open unexpectedly | The code detached without shutting down the browser. | Call browser.close() when your process owns the browser and intends to stop it. |
| A screenshot is blank or navigation hangs | The page may not have loaded, the chosen wait condition may not complete, or the page may require additional state. | Inspect navigation errors and page state; choose a suitable wait condition and handle site-specific consent or loading behavior explicitly. |
8. Performance, reliability, and cost
Connecting to an already running browser avoids making this task responsible for launching that browser, but the connection procedure alone does not guarantee faster captures or a particular reliability level. Runtime still depends on browser availability, network distance, page behavior, chosen wait condition, and the work performed in the page. Reuse a managed browser only when its owner and lifecycle are clear; isolate tasks in a way that fits the host and your application’s requirements.
Puppeteer is software; this connection workflow does not have a per-connection price stated in the referenced API docs. If you use a separately hosted browser, check that provider’s current costs, limits, security, and compatibility directly. Do not infer those details from Puppeteer’s endpoint format.
9. Or skip the browser setup
If you only need a screenshot file or PDF and do not need to control a browser session, ScreenshotNeo is a website screenshot API and MCP server for developers, made by Yorker Media. One GET request returns a PNG, JPEG, WebP, or PDF. The API documentation lists the available parameters.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Cookie and consent banners are accepted like a visitor and removed before the shot, along with more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers say which page verdict applied and whether it was billed. An MCP server lets AI agents, including Claude, Cursor, and other MCP clients, use take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up free for 1,000 screenshots a month, with no card required.
10. FAQ
Can Puppeteer connect to a browser started by another process?
Yes. The browser must expose a Puppeteer-compatible endpoint that the Node.js process can reach. Connect with its WebSocket endpoint or browser URL.
Does disconnecting close open tabs?
No. browser.disconnect() detaches Puppeteer but leaves the browser and its pages running.
Can I use the target website URL as browserURL?
No. browserURL identifies the reachable browser debugging endpoint. The website URL is passed to a page navigation call such as page.goto().
Where should I check exact option support?
Use the ConnectOptions reference matching the Puppeteer version installed in your project, since options and compatibility details can change between releases.


