How to Dispose of a Puppeteer Connection
Use `browser.disconnect()` to detach Puppeteer while Chrome keeps running, `browser.close()` to shut the browser down, or `context.close()` to close one isolated context.
Choose the cleanup method by what should remain alive: use await browser.disconnect() to detach Puppeteer and leave the browser process running; use await browser.close() to shut down the browser and its pages; or use await context.close() to close one non-default browser context and its pages.
| What you want to clean up | Call | What remains |
|---|---|---|
| Puppeteer’s connection only | await browser.disconnect() |
The browser process and its pages remain running. |
| The browser Puppeteer launched | await browser.close() |
The browser and associated pages are closed. |
| One isolated context | await context.close() |
Other contexts and the browser can remain open. |
1. Disconnect Puppeteer and keep Chrome running
Use disconnect() when another process or later task should continue using the browser. It detaches Puppeteer; it does not close pages or stop the browser process.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
const browserWSEndpoint = browser.wsEndpoint();
console.log('Browser endpoint:', browserWSEndpoint);
// Puppeteer disconnects. The browser process and page remain open.
await browser.disconnect();
})();
Save the WebSocket endpoint if you intend to reconnect. Treat it as a connection credential: do not expose it to untrusted users or log it where others can access it.
2. Close the browser and its pages
If this task launched a browser that should not be reused, call close(). It closes the browser and all associated pages.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
} finally {
await browser.close();
}
})();
Putting cleanup in finally ensures the close attempt also runs when navigation or page work throws. If the browser is intended to be shared or reused, do not close it at the end of each task.
3. Close one browser context
A context is useful when a task needs an isolated session. Closing it closes its pages while leaving the browser available. The default browser context cannot be closed with context.close().
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
try {
const context = await browser.createBrowserContext();
try {
const page = await context.newPage();
await page.goto('https://example.com');
console.log(await page.title());
} finally {
await context.close();
}
} finally {
await browser.close();
}
})();
When the browser is shared by multiple jobs, close each job’s non-default context when its work ends. Close the browser itself only when its owner is finished with all work.
4. Reconnect after disconnecting
For a browser that should outlive one Puppeteer client, retain its endpoint, disconnect, then connect again later. The later client can close the browser when its lifecycle is truly finished.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const browserWSEndpoint = browser.wsEndpoint();
await browser.disconnect();
const browser2 = await puppeteer.connect({ browserWSEndpoint });
const pages = await browser2.pages();
console.log(`Reconnected; open pages: ${pages.length}`);
await browser2.close();
})();
This demonstrates that a later connection can manage the still-running browser. Reconnection is not required just to call close() in the process that owns the browser object; use the lifecycle method appropriate to your application.
5. Understand the lower-level Connection API
Puppeteer’s Connection.dispose() is a synchronous, lower-level API that returns void. Its reference does not establish it as the normal application-level cleanup method. For ordinary browser lifecycle cleanup, use the documented Browser or BrowserContext methods above. Check the API reference matching your installed Puppeteer version, since the documented references span different releases.
6. Choose the right cleanup pattern
| Situation | Recommended call | Reason |
|---|---|---|
| A one-off script owns the browser | await browser.close() in finally |
Stops the browser and closes its pages when the script is done. |
| A browser manager will reuse the browser | await browser.disconnect() |
Detaches the current Puppeteer client while leaving the process available. |
| A task uses a separate session in a shared browser | await context.close() |
Closes that task’s pages and context without closing the whole browser. |
| You need to stop a shared browser | Have its owner call await browser.close() |
A client that only disconnected no longer owns an active browser connection to manage. |
7. Troubleshooting
The browser process is still running after cleanup
Cause: browser.disconnect() intentionally leaves it running. Fix: if the task owns the browser and should stop it, call await browser.close() instead.
Pages disappear after cleanup
Cause: browser.close() closes the browser and associated pages. Fix: use browser.disconnect() to preserve them, or close only the task’s non-default context.
context.close() fails for the default context
Cause: Puppeteer does not allow closing the default browser context through this method. Fix: create an isolated context with browser.createBrowserContext() for work that needs context-level cleanup, or close the browser if it should all stop.
Reconnection fails
Cause: the browser may no longer be running, or the saved endpoint may not identify its current WebSocket endpoint. Fix: capture browser.wsEndpoint() before disconnecting and pass that value as browserWSEndpoint to puppeteer.connect(). Verify that the browser is still available and consult documentation for the version in your project.
Cleanup is skipped after an exception
Cause: the cleanup call follows work that throws, without a finally block. Fix: put the matching close() or disconnect() call in finally, as appropriate to the owner’s lifecycle.
A cleanup call is not awaited
Cause: Browser.close(), Browser.disconnect(), and BrowserContext.close() return promises. Fix: await the call so your code observes its completion before proceeding. Connection.dispose() is the synchronous API described separately above.
8. Performance, reliability, and cost considerations
Disconnection keeps the browser process and pages alive, which can support reuse, but it also means that browser resources continue to exist until the browser is closed elsewhere. For short-lived jobs that own their browser, close it in a finally block. For shared browsers, make ownership explicit and close per-task contexts so one job does not keep its isolated pages open unnecessarily.
The supplied Puppeteer references do not give performance benchmarks or resource-cost figures for these choices. Operationally, select the narrowest lifecycle scope that matches ownership, and monitor your own browser processes and job behavior. Do not assume disconnecting means the browser has stopped.
9. Capture a screenshot without managing a browser
If your goal is a website screenshot rather than browser automation, ScreenshotNeo provides a screenshot API and MCP server. Puppeteer remains useful when you need custom browser automation; the API option avoids managing a browser connection for a straightforward capture. See the ScreenshotNeo API documentation.
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 import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
Or skip the browser setup
One GET request returns a screenshot or PDF. Cookie banners, popups, and chat widgets are removed before the shot; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. An MCP server lets AI agents 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 shots.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Get 1,000 screenshots a month free with no card.
10. FAQ
Does disconnecting close open pages?
No. Disconnecting leaves the browser process and its pages running.
Can I close only one page?
This guide covers browser and context cleanup. For page-level lifecycle needs, consult the Puppeteer Page API for your installed version.
Is Connection.dispose() the same as browser.close()?
They are different API levels. The documented application-level choices for browser cleanup are the Browser lifecycle methods; the Connection reference describes dispose() as synchronous.
Which method should a screenshot script use?
If it launches and owns a browser for one capture, close that browser in finally. If you only need a website screenshot, ScreenshotNeo can return the capture through one API request.


