Call await handle.jsonValue() to get the serializable value referenced by a Puppeteer JSHandle in Node.js. If you need only a property or computed result, use handle.evaluate() instead; if you need to keep working with a page-side object or DOM element, retain a handle with evaluateHandle().
Get the value with jsonValue()
jsonValue() returns a promise for a Node.js value representing the serializable portions of the object referenced by the handle. This complete example creates a handle, reads its value, and disposes of the handle when it is no longer needed:
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();
}
The method returns serializable data, not a new live reference to the original page-side object. It does not call an object’s toJSON() method. See the Puppeteer JSHandle.jsonValue() API reference; API details can vary across Puppeteer versions, so check the reference matching the version installed in your project.
Choose the right operation for the result you need
| Need | Use | What you get |
|---|---|---|
| The serializable value referenced by an existing handle | await handle.jsonValue() |
A Node.js value containing serializable portions of the referenced object. |
| One property or a computed result | await handle.evaluate(value => value.someProperty) |
The function’s returned result, rather than the entire object. |
| A property from an existing handle using the page API | await page.evaluate((value) => value.someProperty, handle) |
The function’s returned result; Puppeteer awaits a returned promise. |
| A new object or DOM element to keep working with in the page | await page.evaluateHandle(() => ...) |
A handle to the page-side result; an element result becomes an ElementHandle. |
| A value from a matching descendant of an element | await elementHandle.$eval(selector, node => node.textContent) |
The function’s result for the first matching descendant. |
These distinctions are documented in the JSHandle.evaluate(), Page.evaluateHandle(), and ElementHandle.$eval() API references.
#1 Best Overall
Read a property or extract text from an element
Get only the field you need
Use evaluate() when the full object is unnecessary. The callback executes against the referenced page-side object, and its returned result is transferred back:
const titleHandle = await page.evaluateHandle(() => ({ title: 'Example', count: 3 }));
try {
const title = await titleHandle.evaluate(value => value.title);
console.log(title); // Example
} finally {
await titleHandle.dispose();
}
Get text from a descendant
For an element handle, $eval() is convenient when you want a value from the first matching descendant. Return the text or another specific property, not the DOM node itself:
Rank #2
const heading = await page.$eval('h1', node => node.textContent);
console.log(heading);
Alternatively, retain an element handle and evaluate against it:
const headingHandle = await page.$('h1');
if (headingHandle) {
try {
const heading = await headingHandle.evaluate(node => node.textContent);
console.log(heading);
} finally {
await headingHandle.dispose();
}
}
Why returning a DOM node does not give you useful JSON
page.evaluate() transfers a serialized result across the page-to-Node boundary. A DOM node returned from page.evaluate() may be reconstructed as an empty object ({}), rather than as a usable representation of the element. Return the specific data you need, such as textContent or an attribute, or use evaluateHandle() when you need to keep an element reference. Puppeteer’s JavaScript execution guide explains this boundary.
Handle lifetime and cleanup
A handle refers to an object inside the page. Puppeteer documents that the handle keeps that object from being garbage-collected until the handle is disposed. A handle is also automatically disposed when its frame navigates away or its parent execution context is destroyed. In workflows where a handle remains alive, call dispose() once you are finished with it. A try/finally block is useful when the work might throw, as in the examples above. See the JSHandle API reference.
Errors and troubleshooting
jsonValue() rejects for circular data
The API documents an error when the referenced object cannot be serialized because it contains circularity. Instead of extracting the whole object, use evaluate() to return only the needed fields or build a smaller serializable result in the page.
Rank #4
The result is empty or not the object you expected
Check what the handle refers to and what crosses the serialization boundary. Returning a DOM node from page.evaluate() may produce {}; return a property such as node.textContent, or retain the node with evaluateHandle() and evaluate against that handle.
A handle no longer works after navigation
Handles are tied to page execution contexts. Puppeteer automatically disposes of them when their frame navigates away or their parent context is destroyed. Create a fresh handle after navigation rather than trying to reuse the old reference.
Free tools Windows power users keep installed
One-click scans. No signup required.
Best Value
You expected a custom toJSON() result
jsonValue() does not invoke toJSON(). If you require a particular output shape, explicitly construct and return that shape with evaluate().
Or skip the browser setup
If your goal is a screenshot or PDF rather than a live Puppeteer object, ScreenshotNeo can capture a URL with one request. Its screenshot API and MCP server are described at ScreenshotNeo; API details are in the ScreenshotNeo documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
- Cookie and consent banners, newsletter popups, and chat widgets are removed before capture; each of those steps can be turned off.
- Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed. Responses identify the page verdict and billing status in headers.
- An MCP server provides
take_screenshot,get_page_info, andcapture_pdffor AI agents and MCP clients. - The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots.
Sign up for ScreenshotNeo’s free plan to get 1,000 screenshots a month without a card.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

