ScreenshotNeo

BlogHow-to

How to Get a JSON Value from a Puppeteer Handle

Use JSHandle.jsonValue() to read a Puppeteer handle’s serializable value. Learn when to use evaluate(), how to handle DOM elements, and how to avoid serialization errors.

By the ScreenshotNeo team4 October 20266 min read

Call await handle.jsonValue() to get the serializable value referenced by a Puppeteer JSHandle as a Node.js value. Use handle.evaluate(fn) when you need only a property or computed result, and page.evaluateHandle(fn) when you need to keep a page-side object or DOM element as a handle.

jsonValue() returns the serializable portions of the referenced object. It does not turn a handle into a live Node.js reference to the page object, and it does not call the object’s toJSON() method. Circular structures cannot be serialized and cause an error. See Puppeteer’s JSHandle.jsonValue() API reference.

Get the value with jsonValue()

This runnable example creates a handle to a page-side object, extracts its value, and disposes the handle when finished:

const handle = await page.evaluateHandle(() => ({
  name: 'Ada',
  active: true,
}));

try {
  const value = await handle.jsonValue();
  console.log(value); // { name: 'Ada', active: true }
} finally {
  await handle.dispose();
}

jsonValue() is asynchronous, so await it. The returned value is usable in Node.js like other serializable data. The original handle remains a page-side reference until it is disposed or its execution context is destroyed.

Choose between jsonValue(), evaluate(), and evaluateHandle()

What you need Use What comes back
The serializable value represented by an existing handle await handle.jsonValue() A Node.js value containing the serializable portions
One property or a computed result await handle.evaluate(value => value.title) The function’s returned value
To pass a handle into a page function and return a value await page.evaluate((value) => value.title, handle) The function’s returned value
A new page-side object or DOM element to keep as a reference await page.evaluateHandle(() => ...) A JSHandle, or an ElementHandle when the result is an element
Text or another value from a matching descendant of an element await elementHandle.$eval(selector, node => node.textContent) The callback’s returned value

These operations solve different boundary-crossing problems. jsonValue() extracts serializable data from a reference you already have. evaluate() runs code against the referenced object and returns a value, which can avoid transferring fields you do not need. evaluateHandle() retains the result in the page as a handle. For the official behavior and signatures, see Puppeteer’s JSHandle.evaluate(), Page.evaluateHandle(), and JavaScript execution guide.

Get a property or text instead of the whole value

If you only need one field, return it directly. This is often simpler and safer than serializing a large object:

const title = await handle.evaluate(value => value.title);
console.log(title);

You can also pass a handle as an argument to page.evaluate():

const title = await page.evaluate(value => value.title, handle);
console.log(title);

For an element, extract the desired data rather than returning the DOM node itself. Returning a DOM node from page.evaluate() does not reconstruct the node in Node.js as a useful object; it may result in {}. To read a descendant’s text, use an element helper such as $eval():

const text = await elementHandle.$eval(
  '.article-title',
  node => node.textContent,
);
console.log(text);

If you need to work with the element as a page-side object across operations, keep its ElementHandle. If you need a value, return the specific property or a plain data structure. See the ElementHandle.$eval() API reference.

Serialization limits and edge cases

  • Circular references: an object that refers back to itself cannot be serialized by jsonValue(); Puppeteer documents that serialization fails. Select the fields you need with evaluate(), and return a non-circular result.
  • toJSON() is not called: do not rely on custom JSON conversion behavior to shape the result. Explicitly build the desired plain object in evaluate().
  • DOM elements: a DOM node is a page object, not a useful JSON snapshot. Extract fields such as textContent, attributes, or selected descendant data in the page context.
  • Promises: Puppeteer’s evaluation methods await a returned promise. Await the method call in Node.js as well so errors are caught at the call site.
  • Context or navigation changes: handles are tied to the page execution context. A navigation or destroyed parent context automatically disposes the handle; attempting to use a stale handle cannot recover the old page object.
  • Large objects: avoid transferring an entire object if the caller needs only a few fields. Return a compact object from evaluate() to reduce serialization work and make the output shape explicit.

Handle lifetime and cleanup

A handle keeps its referenced in-page object from being garbage-collected while the handle is alive. Dispose handles explicitly after use when they remain in your workflow. Puppeteer also disposes them when their frame navigates away or the parent execution context is destroyed. A try/finally block is useful when extraction can throw:

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

try {
  const data = await handle.jsonValue();
  await saveData(data);
} finally {
  await handle.dispose();
}

Do not dispose a handle before the last operation that uses it. Conversely, retaining many handles longer than needed can keep page-side objects alive. See Puppeteer’s JSHandle reference.

Troubleshooting

Symptom Likely cause Fix
jsonValue() rejects with a serialization error The referenced value contains circular structure or otherwise cannot be serialized. Use evaluate() to return only the needed, serializable fields. Build a plain result object explicitly.
The result is {} for an element A DOM node was returned as a value from page.evaluate(). Keep it as an ElementHandle with evaluateHandle(), or return its text, attributes, or other data from evaluate().
A custom JSON representation is missing jsonValue() does not call the object’s toJSON(). Call a conversion function deliberately in the page context, or construct the output fields yourself.
An operation fails after navigation The handle was disposed automatically when its frame navigated or its context was destroyed. Wait for the intended page state, then query or create a new handle in the current context.
The script hangs or logs a Promise-like result unexpectedly The promise returned by jsonValue() or an evaluation call was not awaited. Use await handle.jsonValue() and await the surrounding async function or top-level call.
Memory usage grows during repeated extraction Handles may be retained after their page-side references are no longer needed. Dispose each handle in a finally block after its last use.

Or skip the browser setup

If your goal is to get a visual capture of a page rather than inspect its JavaScript object, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. Its capture can accept consent banners and remove more than 60 known consent platforms, newsletter popups, and chat widgets before taking the shot; 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. An MCP server provides take_screenshot, get_page_info, and capture_pdf for AI agents.

For the API parameters and configuration, see the ScreenshotNeo documentation. This cURL request saves a WebP capture of Stripe:

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -o shot.webp

There are 1,000 screenshots a month on the free plan with no card. Paid plans start at $5 for 3,000 screenshots; every feature is available on every plan. Sign up for 1,000 free screenshots a month, with no card required.

Performance, reliability, and cost

jsonValue() requires transferring and serializing the requested value across the page-to-Node boundary. For large page objects, returning just the fields you need with evaluate() keeps the result smaller and makes the contract clearer. Reuse a handle only while its page context is valid, dispose it when finished, and create a fresh one after navigation.

Puppeteer does not provide a cost per jsonValue() call in the cited API documentation. In a typical local Puppeteer workflow, the relevant costs are the browser process and the page work your script performs; this method’s documented concerns are serialization and handle lifetime. Avoid treating an API call as a durable snapshot until it has completed successfully and your code has handled serialization errors.

FAQ

Does jsonValue() return a JSON string?

No. It resolves to a JavaScript value in Node.js representing the serializable portions. Call JSON.stringify() yourself if you specifically need a JSON string, and handle errors for values that cannot be serialized.

Can I get a value from an ElementHandle?

Yes. Use elementHandle.evaluate(node => node.textContent) or a helper such as $eval() to return the desired data. Keep the element handle only when you need the element itself as a page-side reference.

Should I call dispose() after jsonValue()?

Dispose the handle when you are done using it and it remains alive in your workflow. Extracting the value does not itself mean the handle is no longer needed, and disposal releases the page-side reference.

Which Puppeteer version should I check?

Use the API reference matching the version installed in your project. Puppeteer’s documentation versions can change, and the API reference identifies the documented method behavior.