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.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Pass the object as an argument to page.evaluate(), then assign that argument to window inside the browser context:

const data = {
  userId: 123,
  featureFlags: { newHeader: true }
};

await page.evaluate((data) => {
  window.myData = data;
}, data);

This is the safest general pattern for ordinary JavaScript data. Puppeteer serializes the argument across the Node.js–browser boundary; the callback executes in the page, where window.myData becomes available to the current document.

What crosses the Puppeteer boundary

Your Puppeteer script runs in Node.js, while the function supplied to page.evaluate() runs in the page. A variable in the Node.js closure is not automatically visible in that page function. Pass values explicitly as arguments.

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

Basic injection

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();

const data = {
  userId: 123,
  featureFlags: { newHeader: true },
  locale: 'en-US'
};

await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });

await page.evaluate((value) => {
  window.myData = value;
}, data);

const received = await page.evaluate(() => window.myData);
console.log(received);

await browser.close();

The second evaluation is optional; it simply demonstrates that the page can read the global. Objects, arrays, strings, numbers, booleans and null are suitable when they can be serialized by Puppeteer.

Use a stable property name

Choose a name that is unlikely to collide with the site. A namespaced property such as window.__MY_APP_CONFIG__ is easier to identify than a generic name like window.data. If the site already defines the property, decide whether replacement is intentional:

await page.evaluate((value) => {
  Object.defineProperty(window, '__MY_APP_CONFIG__', {
    value,
    writable: false,
    configurable: true,
    enumerable: false
  });
}, data);

Use a normal assignment when page code must update the value. A non-writable property prevents accidental reassignment, but it does not make nested objects immutable.

Inject before site scripts run

page.evaluate() runs only when you call it. If the page’s startup code reads the value during document initialization, it is too late to set it after navigation. Register page.evaluateOnNewDocument() before navigating:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const data = { theme: 'dark', experiment: 'checkout-v2' };

await page.evaluateOnNewDocument((value) => {
  window.__MY_APP_CONFIG__ = value;
}, data);

await page.goto('https://example.com', { waitUntil: 'networkidle0' });

The callback runs after a document is created and before that document’s scripts execute. Puppeteer also invokes it again for later navigations and for newly attached or navigated child frames. That repeated execution affects your design.

Rank #2
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option

Make repeated execution safe

An assignment is normally idempotent, but initialization with side effects is not. Avoid incrementing counters, appending duplicate DOM nodes or registering the same event listener every time the hook runs. For a one-time setup in each document:

await page.evaluateOnNewDocument((value) => {
  if (!window.__MY_APP_CONFIG__) {
    window.__MY_APP_CONFIG__ = value;
  }
}, data);

If each navigation should receive the newest data, register a new hook or use a mutable value deliberately; do not assume an old document’s global survives navigation.

Choose the right Puppeteer API

Need API Timing and behavior
Set a data value in the current document page.evaluate Runs immediately in the current page context and returns a serialized result.
Expose a value before application startup page.evaluateOnNewDocument Runs after document creation, before page scripts; applies again on navigation and child-frame attachment or navigation.
Let page code call Node.js page.exposeFunction Adds a callable function to window. Calls cross back to a Node.js callback and resolve as Promises; the function survives navigation.
Retain a browser-side object reference page.evaluateHandle Returns a JSHandle instead of copying the evaluated value into Node.js.

exposeFunction is a bridge, not object injection

Use this when the page needs an operation implemented in Node.js:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await page.exposeFunction('lookupUser', async (id) => {
  return { id, allowed: id === 123 };
});

Page code can call window.lookupUser(123) and receive a Promise. This does not create a static object such as window.user; it creates a function whose execution returns to Node.js.

When to use evaluateHandle

Use a handle when the object should remain represented in the browser, for example when it is large or when later operations need the same in-page reference:

const handle = await page.evaluateHandle((value) => ({ value }), data);

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

await handle.dispose();

Handles are tied to a frame and execution context. Navigation disposes handles associated with the old document; explicitly dispose handles when finished to avoid retaining browser resources.

Data that does and does not serialize

Usually safe

  • Plain objects and arrays.
  • Strings, numbers, booleans and null.
  • Nested combinations of those values.

Commonly problematic

  • Functions: they are not transferred as callable Node.js functions. Use exposeFunction for a Node callback.
  • Node-specific objects such as streams, sockets and class instances with behavior: pass the data fields you need instead.
  • Circular references: convert them to an acyclic representation first.
  • Live browser objects such as DOM nodes: obtain them in the page or use a JSHandle.

Dates, regular expressions, maps, sets and other special values need deliberate serialization if the receiving code must preserve their type. A JSON round trip can normalize data, but it also loses type information and may reject cycles:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const payload = JSON.parse(JSON.stringify(data));
await page.evaluate((value) => {
  window.__PAYLOAD__ = value;
}, payload);

Do not interpolate serialized input into JavaScript source. Passing it as an argument avoids quoting errors and reduces injection risk when values contain apostrophes, newlines or user-controlled text.

Rank #4
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • Brand: Wiley
  • Set of 2 Volumes
  • A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers

Frames, navigation and timing

Navigation replaces the page context

A global belongs to one document. After page.goto(), a link click that navigates, or a reload, the new document has a new window. Inject again with evaluate(), or use evaluateOnNewDocument() when the value must be present on every navigation.

Child frames

The early-injection hook is invoked for newly attached or navigated child frames. A direct page.evaluate() targets the page’s main frame. To set a value in a particular iframe, obtain its frame and evaluate there:

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

await frame.evaluate((value) => {
  window.__CHECKOUT_CONFIG__ = value;
}, data);

Cross-origin restrictions still apply to what page scripts can access. A global placed in one frame is not automatically a global in another.

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

Debugging and failure modes

“My value is undefined”

  • Wrong order: you read the global before the evaluation completed. Await the call.
  • Navigation afterward: inject after the final navigation, or register evaluateOnNewDocument before navigating.
  • Wrong frame: evaluate in the frame that owns the code reading the value.
  • Name collision: inspect Object.prototype.hasOwnProperty.call(window, 'myData') and use a namespaced name.

“Cannot serialize” or a protocol error

Inspect the payload for cycles, functions, DOM nodes or unsupported host objects. Reduce it to plain data, or move the operation to exposeFunction or a handle. Log a compact summary in Node.js before passing it rather than dumping secrets into page logs.

The site overwrites the property

Install before scripts with evaluateOnNewDocument, choose a less collision-prone name, or define a property descriptor appropriate to your application. A non-writable property can break sites that legitimately expect to assign that name, so use it only when you control the contract.

The value disappears after reload

That is expected for a normal page global. Re-register the new-document hook before the reload, or inject after every navigation event. Avoid assuming a JSHandle remains valid across navigation.

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

Testing the injection

Verify both presence and shape inside the browser:

const result = await page.evaluate(() => ({
  present: Object.prototype.hasOwnProperty.call(window, '__MY_APP_CONFIG__'),
  userId: window.__MY_APP_CONFIG__?.userId,
  hasFlags: typeof window.__MY_APP_CONFIG__?.featureFlags === 'object'
}));

if (!result.present || result.userId !== 123 || !result.hasFlags) {
  throw new Error(`Injection check failed: ${JSON.stringify(result)}`);
}

Run this check after the same navigation and frame selection used by the application. That catches timing errors that a test performed on about:blank would miss.

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

Or skip the browser setup

If your actual goal is a rendered screenshot rather than running Puppeteer yourself, ScreenshotNeo provides a website screenshot API and MCP server. A single GET request returns PNG, JPEG, WebP or PDF. Cookie and consent banners, newsletter popups and chat widgets are removed before capture; bot checks, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing status.

cURL (see 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

Python:

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)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo also offers an MCP server for Claude, Cursor and other MCP clients, so an AI agent can call take_screenshot, get_page_info and capture_pdf. Every plan includes its features; the Free plan provides 1,000 screenshots per month with no card, and paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

Frequently Asked Questions

Does assigning to window make the object available to Node.js?

No. The assignment creates a browser-side global. To read it in Node.js, return a serializable value with page.evaluate or use a JSHandle.

Can I inject a class instance unchanged?

Do not rely on its prototype or methods crossing the boundary. Pass plain fields and reconstruct behavior in the page when necessary.

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

How do I remove an early-injection script?

Keep the identifier returned by the new-document registration and remove that script using Puppeteer’s documented removal method before registering a replacement.

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.