How to Detach a Puppeteer CDPSession
Detach a Puppeteer CDPSession with `await client.detach()`. Learn what stops working afterward, how to clean up safely, and why this does not close the page or browser.
To detach a Puppeteer Chrome DevTools Protocol session, call and await detach() on the session object:
const client = await page.createCDPSession();
// Use client.send(...) and client.on(...) while needed.
await client.detach();
Page.createCDPSession() creates a CDP session attached to the page and returns a promise for a CDPSession. CDPSession.detach() returns Promise<void>. Once detached, that session object no longer emits events and cannot send messages. This method detaches the session from its target; its documentation does not say that it closes the page or browser. The references here are for Puppeteer 25.12.0; check the API documentation matching your installed version if it differs. Puppeteer: CDPSession.detach() · Puppeteer: Page.createCDPSession()
Complete JavaScript example
This example shows the surrounding browser and page setup as well as session cleanup. The browser is closed separately after the session has been detached.
const puppeteer = require('puppeteer');
async function main() {
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
const client = await page.createCDPSession();
try {
// A raw CDP command. The session remains usable until detached.
const result = await client.send('Runtime.evaluate', {
expression: 'document.title',
returnByValue: true,
});
console.log(result.result.value);
// Listen to CDP events with client.on('Event.name', handler) as needed.
} finally {
await client.detach();
}
// Detaching the CDP session does not close this page.
console.log('Page is still available:', await page.title());
} finally {
await browser.close();
}
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
The try/finally around session work makes the intended cleanup order explicit, including when the work throws. It is a useful pattern, not a documented requirement that every session must use this structure. The cited API pages do not promise that repeated detach calls are safe, so call it once as part of the session’s cleanup path.
TypeScript example
The lifecycle is the same in TypeScript. The explicit type is optional when TypeScript can infer it from createCDPSession().
import puppeteer, { type CDPSession } from 'puppeteer';
async function readTitleWithCdp(): Promise<string> {
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
const client: CDPSession = await page.createCDPSession();
try {
const result = await client.send('Runtime.evaluate', {
expression: 'document.title',
returnByValue: true,
});
return String(result.result.value ?? '');
} finally {
await client.detach();
}
} finally {
await browser.close();
}
}
readTitleWithCdp().then(console.log).catch(console.error);
What detaching changes
A CDPSession carries raw Chrome DevTools Protocol messages. Use send() to issue protocol methods and event listeners such as on() to observe protocol events. Puppeteer: CDPSession.send()
- After detachment: do not use that session to send more messages or expect it to emit events. The detached object is no longer a usable channel for CDP traffic.
- The page and browser:
detach()is documented as detaching the CDP session from its target. The method documentation does not say it closes thePageor browser. Close those separately if that is your goal. - If you need CDP later: create a session again with
page.createCDPSession()and use the new session object. Do not treat the detached object as reusable.
Puppeteer documents a readonly detached property and a sessiondetached event as observation surfaces. The cited API references do not specify when the property changes or how event timing relates to the detach promise resolving, so avoid depending on an ordering guarantee that is not documented. CDPSession class · CDPSessionEvents
Cleanup choices and lifecycle scope
| What you want | Action | Scope |
|---|---|---|
| Stop using the raw CDP connection | await client.detach() |
Detaches this CDPSession from its target. |
| Close a page | Use the Puppeteer page lifecycle API for your installed version. | Page lifecycle; separate from detaching a CDPSession. |
| Shut down the browser | Use the Puppeteer browser lifecycle API for your installed version. | Browser lifecycle; separate from detaching a CDPSession. |
The table deliberately does not prescribe page or browser method details: the cited research concerns CDP session creation and detachment, and those other lifecycle APIs should be checked in documentation for the version you use.
Or skip the browser setup
If your goal is to capture a website screenshot rather than issue raw CDP commands, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, 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,
)
r.raise_for_status()
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}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await (await import('node:fs/promises')).writeFile('shot.webp', bytes);
ScreenshotNeo accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; 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. Its MCP server offers take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.
Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| A CDP command fails after cleanup | The code is using a session after detach(). |
Finish all CDP work before detaching. If CDP is needed again, create a fresh session from the page. |
| No more CDP events arrive | The session was detached; detached sessions do not emit events. | Attach a new session if you still need to observe protocol events. |
| The page is still open after detach | This is consistent with the documented scope: detach removes the CDP session from its target, and the method page does not say it closes the page. | Close the page separately using the API for your Puppeteer version if that is the desired lifecycle action. |
| The browser remains running | Detaching a session is not documented as browser shutdown. | Close the browser separately when your application is finished with it. |
| Examples or types differ from the installed package | The references cited here describe Puppeteer 25.12.0, while API surfaces can vary by release. | Consult the documentation matching the package version in your project. |
Performance, reliability, and cost
detach() is an asynchronous lifecycle operation. Await it when later cleanup depends on the session being detached, so the sequence in your code is explicit. The cited API references provide no latency benchmarks, timing guarantees, or special cost figures for detachment; none are assumed here. For reliability, keep session work inside a clear lifetime, detach in a cleanup path, and do not issue commands through the session after that point.
FAQ
Is client.detach() the right method?
Yes. Call await client.detach() on the CDPSession returned by page.createCDPSession().
Does detaching close the page?
The method documentation says it detaches the session from its target; it does not state that it closes the page. Treat page closure as a separate lifecycle operation.
Can I send another command after detaching?
No. Puppeteer documents that a detached session cannot send messages or emit events. Create a new session if you need CDP again.
Does detach() return a value?
Its documented signature is Promise<void>.


