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.

Use page.evaluate() to run JavaScript in the page that is already open. To run code before a page’s own scripts, register page.evaluateOnNewDocument() before navigating. Use page.addScriptTag() when you specifically need a script element, and page.exposeFunction() when browser code must call a function implemented in Node.js.

Choose the right Puppeteer injection method

Need API When it runs and what it returns
Read page state, change the DOM, or run a one-off function page.evaluate() Runs in the current page context and returns the result; Puppeteer waits for a returned Promise.
Set globals or install hooks before application code page.evaluateOnNewDocument() Runs after a document is created but before its scripts. The registration applies to future navigations and relevant child-frame document creation until removed.
Load a URL or inline source as a script element page.addScriptTag() Adds a script element to the main frame and returns an element handle.
Allow JavaScript in the page to call Node.js code page.exposeFunction() Adds a named function to window; calls execute in Node.js and resolve to a Promise in the page.

These APIs solve different timing and boundary problems. A function passed to evaluate() runs in the browser, not in Node.js. A preload is for code that must precede site scripts, not a substitute for a one-time DOM edit after the page is ready. A script tag is useful when script-element loading semantics matter. See Puppeteer’s Page API, evaluateOnNewDocument reference, and addScriptTag reference.

Run JavaScript in the current page with page.evaluate()

Call evaluate() after navigating to the page or after the DOM and state you need are available. The function is serialized and executed in the page’s context. Pass inputs as arguments; do not expect Node.js variables from the surrounding lexical scope to be available inside it.

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 title = await page.evaluate(() => document.title);

const text = await page.evaluate((selector) => {
  const element = document.querySelector(selector);
  return element ? element.textContent : null;
}, '#headline');

console.log({ title, text });

The value returned by the page function is transferred back to Node.js. If the page function returns a Promise, Puppeteer waits for it to settle. Prefer plain serializable values such as strings, numbers, arrays, and ordinary objects for data you need in Node.js.

Pass data explicitly

For example, pass a selector as an argument rather than closing over a Node.js variable:

const selector = '.price';
const price = await page.evaluate((cssSelector) => {
  return document.querySelector(cssSelector)?.textContent ?? null;
}, selector);

This makes the data crossing into the page context explicit. Browser objects and handles are tied to an execution context; they are not interchangeable with ordinary Node.js values.

Coordinate actions that navigate

If a click or injected action can cause navigation, start waiting for navigation at the same time as the action so the event is not missed:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const [response] = await Promise.all([
  page.waitForNavigation(),
  page.click('a.continue'),
]);

The same coordination principle applies if code you run in the page triggers navigation: arrange the navigation wait before starting the action.

Run code before the site’s scripts

Register page.evaluateOnNewDocument() before the navigation you want to affect. Puppeteer invokes the function after a document is created but before that document’s scripts run. This is the right approach for setting a value the application reads during startup or installing an early hook.

await page.evaluateOnNewDocument((value) => {
  Object.defineProperty(window, '__BUILD_LABEL__', {
    configurable: false,
    value,
  });
}, 'test-build');

await page.goto('https://example.com');

As with evaluate(), provide arguments explicitly. A preload registration is not a one-time callback: it remains in effect for later navigations, and the documented lifecycle includes attached or navigated child frames. If the code might run more than once in the same frame context, make initialization safe to repeat—for example, check a flag before installing a listener.

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

Load a preload from a file

For a larger hook, read the source in Node.js and register the source string. Keep the returned registration identifier so you can remove the hook when its scope ends:

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.
const fs = require('node:fs');

const preload = fs.readFileSync('./preload.js', 'utf8');
const registration = await page.evaluateOnNewDocument(preload);

await page.goto(targetUrl);

// Remove this registration before later navigations that should not use it.
await page.removeScriptToEvaluateOnNewDocument(registration.identifier);

Removal stops that registration from being used for subsequent document creation; it does not undo changes already made in a document. Keep the identifier with the test or instrumentation setup that owns the hook.

Add an external or inline script element

Use page.addScriptTag() when you want Puppeteer to add a <script> element to the main frame. Supply either a URL or inline content:

const externalScript = await page.addScriptTag({
  url: 'https://cdn.example.test/library.js',
});

const inlineScript = await page.addScriptTag({
  content: 'window.injectedFlag = true;',
});

The call returns an ElementHandle<HTMLScriptElement> for the inserted element. The page-level method targets the main frame; it does not imply insertion into every frame. For a particular child frame, call that frame’s corresponding frame.addScriptTag() method.

Choose this API for script-element behavior or loading source by URL. If the goal is simply to execute a short function against the current DOM, evaluate() is usually more direct. If the code must run ahead of site scripts, use a preload rather than adding a script after navigation.

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

Let page JavaScript call Node.js with exposeFunction()

Browser code cannot directly access Node.js variables or APIs. To provide a narrow bridge, expose a named function before the page code needs it. Puppeteer adds the function to window; calling it in the page invokes the Node-side implementation and returns a Promise.

await page.exposeFunction('readBuildInfo', async () => {
  return { version: process.env.BUILD_VERSION ?? 'unknown' };
});

await page.evaluate(async () => {
  const info = await window.readBuildInfo();
  document.body.dataset.buildVersion = info.version;
});

Exposed functions remain installed across navigations. Choose a clear name and return only the data the page needs. Treat the bridge as an explicit capability: page code can invoke it, so do not expose sensitive operations or data unnecessarily.

Timing, frames, and content-security policy

  • Current document: use evaluate() when the target page and required DOM state exist. For code that must precede application scripts, install the preload before navigation.
  • Child frames: a preload registration is invoked for relevant child-frame attachment or navigation. A page-level addScriptTag() is a shortcut for the main frame; target a specific frame with its frame API.
  • Repeat execution: preload code may run for more than one document. Make hooks idempotent if repeated installation would duplicate listeners or alter behavior.
  • CSP: Puppeteer documents page.setBypassCSP(true) as a way to bypass page content-security policy, and notes that it takes effect at CSP initialization, usually requiring the call before navigation. Whether it resolves a particular injection problem depends on the site and setup; verify the target behavior rather than assuming it will work universally. See the Page API.

Troubleshooting injection failures

The function cannot see a Node.js variable

Cause: the callback runs in the browser execution context and does not inherit Node.js lexical scope.

Fix: pass the value as an argument to evaluate() or evaluateOnNewDocument(), or expose a specific Node-side function with exposeFunction().

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

The hook runs too late

Cause: the page has already run the application code that reads the value or installs the behavior you intended to change.

Fix: register evaluateOnNewDocument() before goto(), then navigate. For a change that only concerns an already-rendered DOM, use evaluate() at the appropriate point instead.

The script appears only in the main frame

Cause: page.addScriptTag() adds the element to the main frame.

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

Fix: identify the relevant child frame and use its addScriptTag() method. A preload is another option when the code should execute for future frame documents.

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

A preload keeps affecting later pages

Cause: evaluateOnNewDocument() registers persistent behavior across navigations.

Fix: retain the returned identifier and call removeScriptToEvaluateOnNewDocument(identifier) when the registration is no longer needed.

Inline code or a loaded script is blocked

Cause: the target page’s CSP or other site behavior may prevent the script from executing as expected.

Fix: confirm that the script element was added and inspect the page’s errors and policy. If testing a CSP bypass, set it before navigation and validate on the target site; the API does not establish universal compatibility.

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

A returned value is missing or unusable in Node.js

Cause: the result may not be serializable as ordinary data, or may be tied to a browser execution context.

Fix: return a plain object, string, number, or other suitable data structure, and pass inputs explicitly. If you need to interact with a browser element rather than transfer its value, use Puppeteer’s handle-oriented APIs.

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 goal is a screenshot rather than browser-side instrumentation, ScreenshotNeo is a website screenshot API and MCP server: one GET request can return a PNG, JPEG, WebP, or PDF. It can remove cookie banners, popups, and chat widgets before capture; bot checks, blank pages, and failed loads are not billed; and its MCP server lets AI agents take screenshots. The free plan includes 1,000 shots a month with no card; paid plans start at $5 for 3,000 shots. 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

For a Puppeteer workflow that specifically needs custom JavaScript, DOM control, or browser-state inspection, keep using the APIs above. For a screenshot capture, sign up for 1,000 free screenshots a month with no card.

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

Frequently Asked Questions

Does page.evaluate() wait for async JavaScript?

Yes. If the function returns a Promise, Puppeteer waits for it to settle and returns its result.

Can I use these APIs to inject code into every frame?

A preload runs for future document creation, including relevant child-frame attachment or navigation. The page-level addScriptTag() targets only the main frame; use a frame’s API for a specific child frame.

Is there a published speed benchmark for these injection methods?

The cited Puppeteer documentation does not provide a benchmark or universal compatibility statistic for these methods.

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.

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