ScreenshotNeo

BlogHow-to

How to Dispose of a JavaScript Handle in Puppeteer

Dispose of Puppeteer handles with `await handle.dispose()` when you are done. Learn when cleanup is automatic, how to avoid leaks, and how to troubleshoot common cases.

By the ScreenshotNeo team4 October 20265 min read

Call and await dispose() when you are finished with a live Puppeteer handle:

await handle.dispose();

This releases the referenced page object for garbage collection. It does not promise that memory is reclaimed immediately. ElementHandle inherits this method from JSHandle, so the same cleanup rule applies to element handles. See Puppeteer’s JSHandle.dispose() API.

1. What a Puppeteer handle is

A JSHandle is a retained reference to an object in the page’s JavaScript context. APIs such as page.evaluateHandle() can return one. An ElementHandle is a handle to a DOM element and extends JSHandle.

Keeping a handle lets Node.js code refer to that in-page object for later operations. The reference can keep the object from being garbage-collected, so explicitly dispose of a handle when its last use is complete. The Puppeteer page interactions guide recommends locators for ordinary selection and interaction, and notes that a handle returned by lower-level waitForSelector() needs manual disposal.

2. Dispose a JavaScript handle

Await disposal after the final operation that needs the page object:

const handle = await page.evaluateHandle(() => window);

try {
  // Use the handle while it is needed.
  const title = await handle.evaluate(win => win.document.title);
  console.log(title);
} finally {
  await handle.dispose();
}

The finally block is a useful JavaScript cleanup pattern: it still attempts disposal if the work throws. The disposal call is asynchronous, so await it. Do not use the handle after disposing it.

3. Dispose an ElementHandle

Element handles use the same method. This example follows Puppeteer’s documented pattern of passing a handle to page.evaluate(), then disposing it:

const bodyHandle = await page.$('body');

if (bodyHandle) {
  try {
    const html = await page.evaluate(body => body.innerHTML, bodyHandle);
    console.log(html);
  } finally {
    await bodyHandle.dispose();
  }
}

The null check matters: selector methods can return null when no element matches. Puppeteer documents this pattern in its Page.evaluate() API reference.

4. Dispose a handle returned by waitForSelector

If you use the handle returned by waitForSelector(), dispose it once the click, extraction, or other last operation is finished:

const element = await page.waitForSelector('div.example');

try {
  if (element) {
    await element.click();
  }
} finally {
  await element?.dispose();
}

Use the optional call because a selector result may be null. If your code needs additional work with the same element, perform it before disposal.

5. When Puppeteer disposes handles automatically

Puppeteer says handles are automatically disposed when their associated frame navigates away or their parent execution context is destroyed. These lifecycle events clean up handles tied to that context, but they are not a reason to retain handles longer than needed in a long-lived page.

For predictable cleanup, dispose of a still-live handle after its last use. The API describes disposal as releasing the referenced object for garbage collection; it does not promise immediate memory reclamation or a specific amount of memory freed. See the JSHandle API.

6. When not to keep a handle

Use a handle when later code genuinely needs a reference to a particular object in the page. For routine selection and interaction, Puppeteer’s current guide recommends locators:

await page.locator('button.submit').click();

A locator avoids retaining a separate handle in your application code for this ordinary action. For data extraction, consider evaluating an expression that returns a serializable value instead of keeping a page object handle:

const title = await page.evaluate(() => document.title);
console.log(title);

Serialization is different from disposal. jsonValue() returns serializable portions as a vanilla value; it does not document disposal of the original handle. It can throw for circular structures and does not call toJSON. See the JSHandle.jsonValue() API.

7. Troubleshooting

Symptom Likely cause What to do
dispose is not a function The value is not a Puppeteer handle, or it was converted to a plain JavaScript value. Check which API produced the value. Call dispose() on a JSHandle or ElementHandle, not on a string, object returned by evaluation, or locator.
The selector result is null No matching element was found, or the selector wait did not produce an element. Check the selector and page state. Guard the result before using it; optional-call dispose() when the result can be null.
Code fails after disposal The handle was disposed before its final use. Move disposal after all operations that need the page object. If cleanup must happen after errors, wrap the operations in try/finally.
A handle becomes unusable after navigation Its associated frame navigated away, so Puppeteer automatically disposed the handle. Acquire a fresh handle from the current page context after navigation.
jsonValue() fails on a complex object The result may contain circular structure; serialization also does not call toJSON. Return a deliberately serializable subset from the page, or use the handle for the in-page operation and dispose it afterward.
Memory use appears unchanged immediately after disposal Disposal releases the reference for garbage collection; it does not promise immediate reclamation. Dispose handles you no longer need and assess memory over the relevant lifecycle rather than expecting an immediate drop from one call.

8. Performance, reliability, and cost

  • Performance: Dispose handles when finished to avoid retaining page objects unnecessarily. Puppeteer does not document a fixed memory saving or immediate reclamation from a disposal call.
  • Reliability: Keep the handle alive through all operations that depend on it, then clean it up in a finally block. Expect navigation or destruction of the parent context to invalidate its handles.
  • Cost: Handle disposal is a Puppeteer lifecycle operation. The cited API documentation gives no per-handle charge or numeric cost claim.

9. Capture a page without managing a browser

If the task is to capture a website screenshot rather than interact with a page object, ScreenshotNeo provides a website screenshot API and MCP server. The direct GET request returns a screenshot or PDF; the API options and setup are documented at ScreenshotNeo docs.

Or skip the browser setup

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}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

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 step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server offers take_screenshot, get_page_info, and capture_pdf for AI agents. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Every feature is available on every plan. See the API docs for options and headers, then sign up for 1,000 free screenshots a month with no card.

10. FAQ

Does dispose() delete the object from the page?

It releases Puppeteer’s handle reference for garbage collection. The API does not say that it immediately deletes the page object or reclaims memory.

Should I dispose a locator?

Locators are not handles returned for a retained page object. Use the locator for the action; dispose actual JSHandle or ElementHandle instances you keep.

Does converting a handle with jsonValue() dispose it?

The API does not document that behavior. If you still hold a live handle and are done with it, call dispose().