October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan NowOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
browser automation

How to Use Functions Inside Puppeteer’s page.evaluate

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Pass a function to page.evaluate, then pass any Node.js values it needs as arguments after the function. Puppeteer runs the callback in the browser page context and returns its result to Node.js. Treat it as a separate browser-side function: Node.js variables are not automatically available inside it.

How page.evaluate runs your function

Puppeteer describes page.evaluate as evaluating a function in the page’s context and returning the result. That means the callback can use browser-side objects such as document, window, and DOM APIs, but it does not run as an ordinary function in your Node.js scope.

Here is a minimal example that reads the page title and adds a value defined in Node.js:

const suffix = ' — product page';
const title = await page.evaluate(
  suffixFromNode => document.title + suffixFromNode,
  suffix,
);

console.log(title);

The first argument is the callback. The next argument, suffix, is passed into the callback’s first parameter, suffixFromNode. You can supply more arguments after the callback and declare corresponding parameters in it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Pass Node.js variables as arguments

This does not work as many developers expect:

const selector = '.product';

const count = await page.evaluate(() =>
  document.querySelectorAll(selector).length,
);

selector belongs to Node.js, while the callback runs in the page. Puppeteer serializes the callback; it does not carry along its surrounding lexical environment. Pass the value explicitly instead:

const selector = '.product';

const count = await page.evaluate(
  selectorFromNode => document.querySelectorAll(selectorFromNode).length,
  selector,
);

For several related inputs, a plain object can make the boundary clearer:

const result = await page.evaluate(
  ({ selector, limit }) => {
    return Array.from(document.querySelectorAll(selector))
      .slice(0, limit)
      .map(node => ({
        text: node.textContent?.trim() ?? '',
        href: node.href ?? null,
      }));
  },
  { selector: 'a.product', limit: 10 },
);

console.log(result);

Prefer values that can be represented as ordinary data across the browser protocol: strings, numbers, booleans, arrays, and plain objects. If your callback needs to operate on a live DOM object rather than receive copied data, use a supported handle such as an ElementHandle.

Return serializable data, not live page objects

A useful return value is data Node.js can consume: for example, an array of objects containing text and URLs. A DOM node or function is not transferred as a live Node.js object by returning it from page.evaluate; a non-serializable result resolves to undefined.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const cards = await page.evaluate(() =>
  Array.from(document.querySelectorAll('.card')).map(card => ({
    title: card.querySelector('h2')?.textContent?.trim() ?? null,
    url: card.querySelector('a')?.href ?? null,
  })),
);

If you need to keep a reference to an in-page object for additional operations, use page.evaluateHandle. It returns a handle to a remote object rather than copying a serializable value. Dispose of handles when you no longer need them so they do not remain retained unnecessarily.

const handle = await page.evaluateHandle(() =>
  document.querySelector('.card'),
);

// Use the handle for further page-object operations as needed.
await handle.dispose();

Use evaluate when the goal is to bring data back to Node.js; use evaluateHandle when the goal is to keep working with an object in the page.

Use async functions inside page.evaluate

An async callback is supported. If the callback returns a Promise, Puppeteer waits for it to resolve and returns the resolved value:

const price = await page.evaluate(async () => {
  const response = await fetch('/api/price');
  const data = await response.json();
  return data.current;
});

console.log(price);

The fetch in this example runs in the page context. Its URL and access are therefore subject to the page’s browser environment, including origin and browser security rules. If a request fails in the page, diagnose that browser-side failure rather than assuming it is a Node.js request.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choose between evaluate, $eval, $$eval, and evaluateHandle

Use the API that matches what the callback needs and what you want back:

API Selector included? What the callback receives Typical result Promise returned by callback
page.evaluate No automatic selector Only arguments you pass after the callback Copied, serializable data Awaited
page.$eval Yes The first matching element Usually a value read from that element Awaited
page.$$eval Yes An array of matching elements Usually mapped data from the elements Awaited
page.evaluateHandle No automatic selector Only arguments you pass after the callback A handle to an in-page object Awaited

One matching element: $eval

Use $eval when you want a selector’s first match passed directly to the callback:

const inputValue = await page.$eval(
  '#email',
  input => input.value,
);

It also accepts extra arguments after the callback. The selector must match an element; if it does not, the operation cannot pass a matching element to your function.

All matching elements: $$eval

Use $$eval to process the elements matched by a selector as an array:

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const labels = await page.$$eval(
  'label',
  nodes => nodes.map(node => node.textContent?.trim() ?? ''),
);

Like $eval, it accepts additional arguments and waits for a Promise returned from the callback. Return compact data rather than attempting to send the DOM elements themselves back as ordinary values.

TypeScript: type DOM elements explicitly when needed

Puppeteer’s current API signatures model page.evaluate with a generic function and infer the result as a Promise of the awaited callback return type. For $eval and $$eval, TypeScript may infer only the general Element or Element[] types. Annotate a callback parameter when you need a more specific DOM subtype:

const value = await page.$eval(
  '#email',
  (el: HTMLInputElement) => el.value,
);

Use the actual element subtype for the selected control: for example, an input, textarea, or button. If a type error says a property does not exist on Element, first check whether the selector can match another kind of element, then narrow or annotate the type accordingly. A type annotation informs TypeScript; it does not change which element the selector matches at runtime.

Complete example: extract product links

This example passes a selector and result limit from Node.js, runs DOM work in the page, and returns plain objects:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const selector = 'a.product';
const limit = 10;

const products = await page.evaluate(
  ({ selector, limit }) => {
    return Array.from(document.querySelectorAll(selector))
      .slice(0, limit)
      .map(link => ({
        text: link.textContent?.trim() ?? '',
        href: link.href || null,
      }));
  },
  { selector, limit },
);

console.log(products);

Keep the division of work straightforward: construct inputs in Node.js, pass them into the callback, use page APIs inside it, and return data that Node.js can serialize and use. If the callback needs asynchronous page-side work, declare it async and return the value you need.

Troubleshooting common page.evaluate errors

ReferenceError: selector is not defined

Cause: The callback refers to a Node.js variable without receiving it as an argument.

Fix: Add a callback parameter and pass the variable after the function, or package several values in a plain object.

The result is undefined when returning an element

Cause: A DOM node is a live browser object, not ordinary return data that evaluate copies into Node.js.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Fix: Return the element’s needed properties as a plain object, or use evaluateHandle when you need to retain a reference to the in-page object.

The callback cannot use document or window

Cause: The callback is not executing in the page context as expected, or the call is being made before the intended page is available.

Fix: Confirm that you are calling page.evaluate on the Puppeteer page you intend to inspect. Use browser globals inside its callback; use Node.js APIs outside it.

A function works in source code but fails after transpilation

Cause: Puppeteer serializes functions using Function.prototype.toString(). A transpiler can change the emitted function in a way that is incompatible with serialization.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Fix: Inspect the function shape produced by your build, simplify the callback, and avoid relying on syntax or wrappers that require surrounding runtime code. Keep page callbacks self-contained and pass their inputs explicitly.

$eval does not find an element

Cause: The selector does not match an element in the page when the method runs.

Fix: Verify the selector against the page DOM and ensure the page has reached the point where the element exists. If you need to process zero or many matches, consider $$eval instead.

A page-side async operation rejects

Cause: The Promise returned by the callback rejects—for example, because a page-side fetch or JSON parse fails.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Fix: Check the failing operation in the browser context and return only after it succeeds. Awaiting a Promise means Puppeteer waits for its outcome; it does not turn a rejected Promise into a successful value.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your task is to get a website screenshot rather than execute custom page-side logic, ScreenshotNeo returns a PNG, JPEG, WebP, or PDF from one GET request. Its API can accept cookie and consent banners like a visitor and remove 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 the response identifies the page verdict and billing status in headers.

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

See the ScreenshotNeo API documentation for request options. An MCP server also lets Claude, Cursor, and other MCP clients use take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 screenshots a month without a card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, with no card required.

Frequently Asked Questions

Does page.evaluate wait for a Promise returned by its callback?

Yes. Puppeteer waits for the Promise to resolve and returns its resolved value; a rejected Promise still fails.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Can I pass more than one argument to page.evaluate?

Yes. Pass additional values after the callback and define corresponding callback parameters, or pass a single object containing the inputs.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
Windows Errors? Fix Them Before They SpreadFree repair scan
Outdated Drivers Are Slowing You DownFree scan - exact matches

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.