ScreenshotNeo

BlogHow-to

How to Get an Object Property with Puppeteer

Use page.evaluate for a plain property value, evaluateHandle and getProperty for an in-page object, or $eval for a DOM element.

By the ScreenshotNeo team4 October 20269 min read

To get a plain property value from an object with Puppeteer, return it from page.evaluate:

const value = await page.evaluate(obj => obj.propertyName, obj);

If the object exists only in the page, use evaluateHandle to retain a reference, then getProperty and jsonValue to read a serializable value. For a property on a DOM element, use page.$eval. The right choice depends on where the object lives and whether you need a value or a live in-page reference. See the official Page.evaluate, Page.evaluateHandle, JSHandle.getProperty, and Page.$eval references for the version you have installed; the inspected docs span Puppeteer 25.1.0–25.12.0.

Choose the API for the object you have

Situation Use What you get
You can pass the object into the page callback and need an ordinary result page.evaluate(fn, arg) The callback’s returned value, serialized back to Node.js
The object exists in the page and you need a retained reference page.evaluateHandle(fn) A handle that refers to the in-page object
You already have an object handle and need one property handle.getProperty(name) A handle for the property; call jsonValue() for its serializable value
You need a property of a DOM element matched by a selector page.$eval(selector, fn) The callback result for the first match; throws if none exists
You need a descendant inside an element handle elementHandle.$eval(selector, fn) The callback result for the first matching descendant
The object is in an iframe Evaluate through its Frame A result from that frame’s execution context

Get a plain property with page.evaluate

page.evaluate runs its callback in the browser page context. Pass the object as an argument and return the property you want. Puppeteer waits if the callback returns a promise.

const object = {
  name: 'Ada',
  settings: { theme: 'dark' }
};

const name = await page.evaluate(obj => obj.name, object);
const theme = await page.evaluate(obj => obj.settings.theme, object);

console.log(name);  // 'Ada'
console.log(theme); // 'dark'

This is the simplest approach when the result can be sent back as a serializable value. Use dot notation for a known property name, such as obj.name. For a key held in a variable, use bracket notation:

const key = 'name';
const value = await page.evaluate((obj, propertyKey) => obj[propertyKey], object, key);
console.log(value); // 'Ada'

Arguments after the callback are passed to it in order. This also works when a property is nested. If an intermediate value could be missing, optional chaining prevents a dereference error:

const city = await page.evaluate(
  obj => obj?.address?.city,
  object
);

Decide how to handle absence deliberately. A missing property and a property whose value is undefined both produce undefined; if you need to distinguish cases, return an explicit presence flag along with the value.

Read a property from an object that lives in the page

If the page creates the object, obtain a handle to it. Use getProperty to get a handle for one property, then jsonValue when you want a serializable value in Node.js.

const objectHandle = await page.evaluateHandle(() => window.someObject);

try {
  const propertyHandle = await objectHandle.getProperty('propertyName');
  try {
    const value = await propertyHandle.jsonValue();
    console.log(value);
  } finally {
    await propertyHandle.dispose();
  }
} finally {
  await objectHandle.dispose();
}

Replace propertyName and window.someObject with the property and page object you need. Disposing handles after use releases their references. Navigation or destruction of the execution context also disposes handles, but explicit cleanup makes their lifetime clear.

If you only need the value once, you can avoid keeping a handle:

const value = await page.evaluate(() => window.someObject.propertyName);

Use a handle when you need to continue interacting with the in-page object, or when the value cannot be represented as an ordinary serialized result. jsonValue() returns a vanilla representation of serializable portions and does not call an object’s toJSON function. For non-serializable values or live objects, keep using handles and page-side evaluation.

Get an element’s value with Puppeteer

For a DOM property such as an input’s value, use selector-scoped evaluation. $eval passes the first matching element to the callback and throws if there is no match.

const email = await page.$eval(
  'input[name="email"]',
  element => element.value
);
console.log(email);

Read an attribute with getAttribute, or return several related properties from the same matched element:

const details = await page.$eval('#profile', element => ({
  id: element.id,
  text: element.textContent,
  title: element.getAttribute('title')
}));

To search inside an element you already selected, call $eval on its handle:

const panel = await page.$('#account-panel');
if (!panel) {
  throw new Error('Account panel was not found');
}

try {
  const status = await panel.$eval('.status', element => element.textContent);
  console.log(status);
} finally {
  await panel.dispose();
}

For pages where the element appears asynchronously, wait for it before reading. A locator can retry its readiness checks; its wait() method returns a serialized value, while waitHandle() waits for a handle. Use the locator approach when the element’s availability is the problem, not just to add machinery to an already available object.

Pass values into the page context correctly

A common cause of undefined or a reference error is assuming the evaluation callback can see Node.js variables through its closure. The callback runs in the page, so pass needed values as arguments:

const propertyName = 'title';
const result = await page.evaluate(
  key => document.querySelector('h1')?.[key],
  propertyName
);

The value passed as an argument must be transferable to the page. Do not rely on a Node.js object reference becoming the same live object inside the browser. For page-side objects, use a handle or perform the property access directly inside evaluate.

Evaluate in the right frame

Each iframe has its own execution context. If the target object belongs to a frame, evaluate through that frame rather than the top-level page:

const frame = page.frames().find(candidate => candidate.url().includes('/embedded'));
if (!frame) {
  throw new Error('Target frame was not found');
}

const value = await frame.evaluate(() => window.someObject.propertyName);

Frame evaluation follows the same basic model as page evaluation, but runs in that frame’s context. A selector in the top-level document cannot directly select a node inside an iframe; find the frame and query or evaluate there.

Asynchronous values and missing properties

Evaluation methods wait for a promise returned by the callback. That lets you await page-side asynchronous work:

const value = await page.evaluate(async () => {
  const result = await window.loadSomeValue();
  return result.propertyName;
});

Handle the ordinary JavaScript edge cases in the callback: missing intermediate objects, dynamic keys, getters that throw, and values that cannot be serialized. For example, to report whether a property exists separately from its value:

const result = await page.evaluate(obj => ({
  exists: Object.prototype.hasOwnProperty.call(obj, 'propertyName'),
  value: obj.propertyName
}), object);

Use in instead of hasOwnProperty if inherited properties should count. If a getter can fail, catch the error in the page callback and return a structured result that your Node.js code can handle.

Complete runnable example

This example launches Chromium, creates a page with both a JavaScript object and an input, reads their properties, and closes the browser even if a read fails. Install Puppeteer in your project with npm install puppeteer, then save this as get-property.cjs and run node get-property.cjs.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();
    await page.setContent(`
      <input name="email" value="ada@example.com">
      <script>
        window.profile = { name: 'Ada', preferences: { theme: 'dark' } };
      </script>
    `);

    // Ordinary property value from a page-side object.
    const name = await page.evaluate(() => window.profile.name);

    // Nested property from an object passed into the page callback.
    const theme = await page.evaluate(
      profile => profile.preferences.theme,
      { preferences: { theme: 'dark' } }
    );

    // DOM property on a selector-matched element.
    const email = await page.$eval('input[name="email"]', input => input.value);

    // Retained in-page reference and one property handle.
    const profileHandle = await page.evaluateHandle(() => window.profile);
    try {
      const nameHandle = await profileHandle.getProperty('name');
      try {
        console.log('Handle property:', await nameHandle.jsonValue());
      } finally {
        await nameHandle.dispose();
      }
    } finally {
      await profileHandle.dispose();
    }

    console.log({ name, theme, email });
  } finally {
    await browser.close();
  }
})().catch(error => {
  console.error(error);
  process.exitCode = 1;
});

Troubleshooting

Symptom Likely cause Fix
ReferenceError for a Node variable in evaluate The callback runs in the page context and cannot capture Node’s lexical scope Pass the value as an argument, or read a page-side value inside the callback
Result is undefined Wrong property name, missing intermediate object, or property value itself is undefined Check spelling and object shape; use optional chaining and return a presence flag if needed
$eval fails because there is no element The selector matched nothing, or the element has not appeared yet Check the selector; wait for the element or use a locator that retries readiness checks
Value is an object handle rather than a plain value evaluateHandle and getProperty return handles Call jsonValue() for serializable data; retain the handle for live in-page access
Serialization error or incomplete value The result contains a function, DOM node, circular reference, or another non-serializable value Return only the fields needed, or keep a handle and operate on the value in the page
Execution context was destroyed Navigation replaced the page or frame context during the operation Wait for the intended navigation or element, then run the evaluation in the new context
Frame lookup returns no value The object is in a different frame than the one being evaluated Inspect the page’s frames and evaluate through the frame that owns the object
A handle stops working after navigation Navigation destroyed the context that owned the handle Acquire a fresh handle after navigation

Performance, reliability, and cost

For one property, return only the needed value from a single evaluation. This avoids transferring an entire object when most of it is unused. If several properties are needed from one object, return a small object containing just those fields in one callback. Keep page-side computation inside the callback when that reduces the data sent back.

Use handles when retaining a live reference matters, and dispose of them promptly. Avoid repeatedly evaluating against stale handles across navigation. If the value depends on a selector or asynchronous page state, wait for the actual condition instead of relying on a fixed delay. Puppeteer itself is a browser automation library; browser startup, navigation, page scripts, and resource loading are the main work around a property read. Reuse a browser where appropriate in a long-running process, while closing pages and browsers when their work is done.

There is no per-property charge from these APIs. Operational cost comes from running the browser and the resources needed by the automation environment. A remote screenshot API is a different option when the goal is an image rather than retrieving an object value.

Or skip the browser setup

If your goal is a clean page image rather than a JavaScript value, ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. Its one-call API captures a URL as PNG, JPEG, WebP, or PDF. It removes cookie and consent banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, and failed loads are never billed; and its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000. Every feature is on every plan. See the 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}`);
const bytes = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));

Replace YOUR_API_KEY with your key. The returned result is an image response, not a JavaScript property value. Sign up for 1,000 free screenshots a month with no card.

FAQ

How do I get a property from an object in Puppeteer?

For a plain result, return it from page.evaluate. For a page-only object you need to retain, use evaluateHandle and getProperty.

How do I get an element’s value with Puppeteer?

Use page.$eval(selector, element => element.value), and make sure the selector matches an element.

Why is my variable undefined inside page.evaluate?

Evaluation runs in the browser context, separate from Node.js closure scope. Pass Node.js values as callback arguments or access browser-side values inside the callback.

Should I use evaluate or evaluateHandle?

Use evaluate when you need a returned value. Use evaluateHandle when you need to keep referring to an in-page object or value that is not suitable for serialization.

Does Puppeteer wait for an async property read?

Yes. Evaluation methods wait for a promise returned by the callback to resolve.