ScreenshotNeo

BlogHow-to

How to Convert a Puppeteer Handle to a String

Use `handle.toString()` for a handle’s debug representation. To get an element’s text or data, evaluate the value you actually need.

By the ScreenshotNeo team4 October 20267 min read

For a Puppeteer handle’s string representation, call handle.toString(). Puppeteer describes this as useful during debugging. It does not convert the referenced element’s text or serialize the referenced object.

Use handle.evaluate() when you want a value from the page, such as an element’s text or an attribute. Use handle.jsonValue() for the serializable portions of the referenced value. If you need the evaluated result to remain a handle, use page.evaluateHandle().

1. Choose the output you need

What you need Use What you get
A description of the handle for debugging handle.toString() A string representation of the Puppeteer handle
Text, an attribute, or a transformed DOM value handle.evaluate(fn) The function’s returned value
Serializable portions of the referenced value handle.jsonValue() A value transferred out of the page context
A continuing reference to an evaluated page object page.evaluateHandle(fn) A handle wrapping the result

The key distinction is between the handle and the object it refers to. Calling toString() describes the handle; it does not read the DOM node’s text. Puppeteer’s JSHandle API reference documents these handle methods and notes that toString() is useful for debugging.

2. Runnable JavaScript example

This example launches Chromium, gets a handle to an element, prints the handle’s representation, then separately reads the element’s text and an attribute. It also demonstrates jsonValue() and disposes the handles when finished.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ headless: true });
  try {
    const page = await browser.newPage();
    await page.setContent('<h1 id="title">Hello from the page</h1>');

    const handle = await page.$('#title');
    if (!handle) {
      throw new Error('Could not find #title');
    }

    // A representation of the handle itself, useful while debugging.
    console.log('Handle:', handle.toString());

    // Values read from the referenced DOM element.
    const text = await handle.evaluate(element => element.textContent);
    const id = await handle.evaluate(element => element.getAttribute('id'));
    console.log('Text:', text);
    console.log('id:', id);

    // For an element, jsonValue() returns its serializable value,
    // not a DOM serialization or its text content.
    const value = await handle.jsonValue();
    console.log('JSON value:', value);

    await handle.dispose();
  } finally {
    await browser.close();
  }
})().catch(error => {
  console.error(error);
  process.exitCode = 1;
});

Run it in a directory with Puppeteer installed. For example, initialize a project with npm init -y, install Puppeteer with npm install puppeteer, save the code as handle.js, and run node handle.js. The toString() output is a debugging representation; do not parse it or depend on a particular format for application logic.

3. Get text or other page data

Text content

const text = await handle.evaluate(element => element.textContent);

textContent returns the text content of the element and its descendants. If you need a different DOM property, return that property from the function instead.

Visible text

const visibleText = await handle.evaluate(element => element.innerText);

Choose the property that matches your need: textContent reads the DOM text content, while innerText is the element’s rendered text. Neither is the handle’s own string representation.

Attribute or a transformed value

const href = await linkHandle.evaluate(element => element.getAttribute('href'));
const summary = await handle.evaluate(element => ({
  tag: element.tagName,
  text: element.textContent.trim(),
}));

The function passed to evaluate() runs in the page context, with the current handle as its first argument. Return the specific data your Node.js code needs rather than trying to stringify the handle.

4. Use jsonValue() for serializable values

const value = await handle.jsonValue();

jsonValue() retrieves serializable portions of the referenced value. Puppeteer documents that it does not call the referenced object’s toJSON() method. It is therefore not a replacement for choosing and returning a page-side value with evaluate(), particularly when you need a deliberate data shape.

For a plain object created in the page, jsonValue() may be convenient. For a DOM element, explicitly evaluate the text, attributes, or properties you want. See the JSHandle reference for the documented behavior.

5. Use evaluate() or evaluateHandle() at page level

Use page.evaluate() when you need the evaluated result as a value. Use page.evaluateHandle() when you need to keep working with the result as a page object handle.

// Returns the evaluated value to Node.js.
const title = await page.evaluate(() => document.querySelector('h1')?.textContent ?? null);

// Returns a handle to the evaluated page object.
const titleHandle = await page.evaluateHandle(() => document.querySelector('h1'));
if (titleHandle) {
  console.log(titleHandle.toString());
  const titleText = await titleHandle.evaluate(element => element?.textContent ?? null);
  console.log(titleText);
  await titleHandle.dispose();
}

The distinction is documented in Puppeteer’s page API reference: evaluate() returns the result, while evaluateHandle() wraps the result in a handle.

6. Handle lifecycle and cleanup

A handle keeps its referenced object from being garbage-collected until the handle is disposed. Dispose handles when you are done with them:

const handle = await page.$('.result');
if (handle) {
  try {
    const text = await handle.evaluate(element => element.textContent);
    console.log(text);
  } finally {
    await handle.dispose();
  }
}

Puppeteer also documents automatic disposal when the associated frame navigates or the parent execution context is destroyed. That automatic cleanup does not replace disposing handles you have finished using during a long-running page session. See the JSHandle lifecycle documentation.

7. Common errors and fixes

Symptom Likely cause Fix
toString() does not return the element’s text It represents the handle, not the referenced DOM content. Read the desired value with handle.evaluate(element => element.textContent) or another specific property.
Cannot read properties of null, or the handle is missing The selector did not match an element when queried. Check the selector, wait for the element when it is created asynchronously, and handle a missing result before calling methods.
Evaluation fails after navigation Navigation can destroy the frame’s execution context and dispose its handles. Wait for the intended page state, then query the element again in the current page context.
jsonValue() does not produce the expected JSON shape It returns serializable portions and does not invoke the object’s toJSON(). Use evaluate() to explicitly construct and return the fields you need.
A handle operation fails after disposal The handle was released and is no longer usable. Do not reuse it after dispose(); obtain a new handle if the page context is still valid.
Code expects evaluateHandle() to return a plain value That method returns a handle around the result. Use page.evaluate() for a returned value, or evaluate/read the handle if you need to retain it.

8. Performance, reliability, and cost notes

  • Performance: Use the narrowest operation that returns what you need. A direct toString() is for inspecting the handle; page-side data requires an evaluation. Avoid repeatedly evaluating the same fields when one evaluation can return a small object containing them.
  • Reliability: Treat a handle as a temporary reference tied to its page execution context. Navigation or context destruction can invalidate it. Query after the relevant navigation and check selectors that may not match.
  • Memory: Dispose of handles when finished, especially in loops or long-lived sessions, so they do not retain page objects unnecessarily.
  • Cost: Puppeteer is browser automation code you run in your own environment. The cited API references do not specify a per-handle or per-call price; hosting and browser runtime costs depend on your setup.

9. Or skip the browser setup

If your goal is to capture a website rather than inspect a Puppeteer handle, ScreenshotNeo provides a website screenshot API and MCP server. It does not convert Puppeteer handles; it gives you a screenshot or PDF from a URL. 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 banners are accepted and removed before capture; newsletter popups and chat widgets are removed too. Each step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing. Response headers identify the page verdict and whether the request was billed.
  • An MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf.
  • The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots.

Sign up for ScreenshotNeo’s free plan to get 1,000 screenshots a month with no card.

10. FAQ

Does JSHandle.toString() return JSON?

No. It returns a string representation of the handle for debugging. Use jsonValue() for serializable portions of the referenced value, or evaluate a specific value yourself.

Can I call toString() on an ElementHandle?

An element handle is a kind of handle, so its toString() representation is still about the handle. To get element text, call evaluate() and return the desired DOM property.

When should I use evaluateHandle()?

Use it when the result should remain a handle in the page context. If you only need a value in your Node.js code, use evaluate().