How to Manage Browser Instances with Puppeteer
Learn when to launch or connect to a Puppeteer browser, how to isolate tasks with browser contexts, and how to clean up without leaking processes.
Puppeteer can either start a browser process that your script owns or connect to a browser that another system started. Use puppeteer.launch() for the first case and puppeteer.connect() for the second. Within either browser, use browser contexts to isolate tasks. When cleanup is finished, call browser.close() to shut down the browser, or browser.disconnect() to detach Puppeteer while leaving the browser process running.
This distinction is the core of browser lifecycle management: decide who owns the process, how much state tasks may share, and which resource your cleanup code is responsible for closing.
1. Choose whether to launch or connect
| Method | Use it when | Lifecycle responsibility |
|---|---|---|
puppeteer.launch() |
Your application should create and manage the browser process. | Your code normally closes it with browser.close(). |
puppeteer.connect() |
A browser is already running, such as one managed by another service or process. | Your code disconnects with browser.disconnect() if the process should remain available. |
The Puppeteer Browser management guide documents both workflows. Connecting does not provision a remote browser; you must obtain the endpoint from the system that runs that browser.
2. Install Puppeteer and launch a browser
In a Node.js project, install Puppeteer:
npm install puppeteer
This example launches a browser, creates an isolated context, opens a page, and reliably cleans up both context and browser:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const context = await browser.createBrowserContext();
try {
const page = await context.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
console.log(await page.title());
} finally {
// Closing a created context also closes pages in that context.
await context.close();
}
} finally {
// Close the browser process this script launched.
await browser.close();
}
Use try/finally around owned resources. If navigation or page work throws, the cleanup still runs. Context cleanup is useful when a browser serves multiple tasks; browser cleanup belongs at the boundary where the process itself should end.
3. Configure launch behavior for your runtime
puppeteer.launch() accepts launch options. The relevant settings include browser selection, Chrome channel, executable path, headless mode, user data directory, startup timeout, signal handling, and additional browser arguments. Check the LaunchOptions API reference for the Puppeteer version installed in your project: defaults and support can vary by option and browser.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({
headless: true,
timeout: 30_000,
// userDataDir: './browser-profile',
// executablePath: '/path/to/chrome',
// args: ['--some-browser-argument'],
});
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
console.log(await page.title());
} finally {
await browser.close();
}
| Option | What to consider |
|---|---|
headless |
Choose whether the browser runs with a visible window, according to the installed Puppeteer version’s supported values. |
channel |
Select a Chrome channel when that channel is installed and appropriate for the environment. |
executablePath |
Use a specific browser binary when needed. Puppeteer guarantees compatibility with its bundled browser; treat another executable as a compatibility choice to validate. |
userDataDir |
Choose a profile directory when browser profile state must be persisted or deliberately managed. Avoid unintentionally sharing a profile between concurrent jobs. |
timeout |
Set how long launch may take before startup fails. Match it to the runtime rather than assuming every environment starts at the same speed. |
args |
Pass browser arguments needed by the runtime. Arguments are browser-specific; verify them against the browser and deployment. |
| Signal handling | Launch options include behavior related to process signals. Review the installed version’s API reference before changing signal handling, especially in a process manager or container. |
Keep the default bundled browser unless a specific deployment requirement calls for another executable. If you change the executable, validate launch, navigation, and shutdown in that environment.
4. Use browser contexts to isolate tasks
A browser process can contain multiple pages. A browser context groups pages and separates their site state from pages in other contexts. The context API documents that contexts do not share cookies or cache; local storage is also isolated between contexts. Closing a created context closes its associated pages. The default browser context is special and cannot be closed.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const [accountA, accountB] = await Promise.all([
browser.createBrowserContext(),
browser.createBrowserContext(),
]);
try {
const pageA = await accountA.newPage();
const pageB = await accountB.newPage();
await Promise.all([
pageA.goto('https://example.com'),
pageB.goto('https://example.org'),
]);
// Cookies and storage are separated by context.
} finally {
await Promise.all([accountA.close(), accountB.close()]);
}
} finally {
await browser.close();
}
Use a separate context for independent users, jobs, or sessions when they must not share cookies or cache. Use multiple pages in one context when pages should share that context’s browser state. A context isolates browser state, but it is not a separate browser process.
5. Connect to an existing browser
When another process or service owns the browser, connect with its WebSocket endpoint. The endpoint must come from that browser deployment; Puppeteer does not create the remote service as part of connect().
import puppeteer from 'puppeteer';
const browser = await puppeteer.connect({
browserWSEndpoint: process.env.BROWSER_WS_ENDPOINT,
});
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
console.log(await page.title());
} finally {
// Detach this Puppeteer client. The browser process remains running.
browser.disconnect();
}
Set BROWSER_WS_ENDPOINT in the environment where this program runs. Treat it as deployment configuration and protect access to it. Use browser.close() only when the connected browser is meant to be shut down as part of your workflow. The browser-management guide states: “Unlike browser.close(), browser.disconnect() does not shut down the browser or close any pages.”
6. Close, disconnect, and clean up correctly
| Operation | Effect | Typical use |
|---|---|---|
context.close() |
Closes a created browser context and its pages. | Finish an isolated task while keeping the browser available. |
browser.close() |
Closes the browser and its associated pages. | End a browser process launched for the job, or intentionally shut down the connected browser. |
browser.disconnect() |
Detaches Puppeteer without shutting down the browser process or closing its pages. | Finish a client session while a separately managed browser remains available. |
Do not confuse detaching with cleanup of the remote process. If your script launched the browser and simply disconnects, the process remains running. Conversely, closing a browser supplied by another system may disrupt other clients. Align cleanup with process ownership.
7. Handle common lifecycle edge cases
- A navigation fails: put context and browser cleanup in
finallyblocks so an exception does not skip resource release. - A task creates several pages: close the task’s context when it ends; its pages close with it.
- Several jobs need separate sessions: create one context per job and close each context after its job finishes.
- A shared browser is managed elsewhere: disconnect the Puppeteer client when finished; do not close the browser unless the owner expects that.
- You use the default context: do not call
close()on it. Create a browser context when you need a closable isolated unit. - The browser is remote: use the WebSocket endpoint from its actual deployment and account for endpoint availability and network access.
8. Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
launch() times out |
The browser did not start within the configured startup timeout, or the runtime cannot start the selected executable. | Check the executable and runtime configuration, then choose a timeout that fits the environment. Review launch options supported by the installed Puppeteer version. |
| Custom Chrome fails to launch or behaves differently | The selected binary differs from Puppeteer’s bundled browser. | Try the bundled browser first. If a custom executable is required, validate that exact browser and version in the target environment. |
connect() cannot reach the browser |
The WebSocket endpoint is missing, stale, malformed, or unreachable from the client environment. | Obtain the current endpoint from the browser’s owner and check deployment network access. connect() does not provision a browser service. |
| The browser remains running after the script exits | The script called disconnect() or did not close a browser it launched. |
If your script owns the process, call browser.close() in a finally block. |
| Other work loses its browser when a task ends | A client called browser.close() on a process shared with other users or jobs. |
Use browser.disconnect() for client cleanup when the shared process must remain alive. |
| Cookies or cached state appear in another task | The tasks are using the same browser context or shared profile configuration. | Use separate browser contexts for independent task state. Review whether a shared userDataDir is appropriate. |
| Closing the default context fails | The default context cannot be closed. | Create a separate context with browser.createBrowserContext() when you need a context that can be closed. |
9. Performance, reliability, and cost considerations
These lifecycle APIs do not imply a particular throughput or resource cost. Measure behavior in the runtime and workload you operate; the Puppeteer documentation cited here does not publish browser-instance performance benchmarks for this task.
- Process ownership: decide which service starts and ends each browser process. A clear owner makes shutdown and recovery behavior easier to reason about.
- Context lifetime: close task contexts when their work is done, especially when a process handles repeated jobs.
- Isolation: contexts separate cookies, local storage, and cache. They allow task-level separation while using one browser process.
- Startup configuration: select the browser and startup timeout for the actual runtime. Custom executables require compatibility validation.
- Reliability: use structured cleanup with
finally, and distinguish a client disconnect from a browser process shutdown. - Cost: the supplied Puppeteer references do not specify hosting prices or a cost per browser instance. If a separate system hosts the browser, consult that system’s pricing and measure its resource use.
10. Or skip the browser setup
If your task is simply to capture a website screenshot, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns an image or PDF; 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}`);
- Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot; 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 gives AI agents, including Claude, Cursor, and other MCP clients, the
take_screenshot,get_page_info, andcapture_pdftools. - The free plan includes 1,000 screenshots per 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.
11. Frequently asked questions
Can one Puppeteer browser have multiple pages?
Yes. A browser can contain multiple pages, and pages can be organized into browser contexts.
Does disconnecting close pages?
No. browser.disconnect() detaches Puppeteer while leaving the browser process and its pages running.
Can I close the default browser context?
No. Create a separate context when you need an isolated context that can be closed.
Does Puppeteer guarantee compatibility with any installed Chrome?
Puppeteer guarantees it works with its bundled browser. Treat a different executable as a compatibility choice to validate in your environment.


