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.
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.
#1 Best Overall
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:
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →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
- 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.
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.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →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.
Rank #3
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().
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
- 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.
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.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Repair Windows errors before they cause bigger problems3Fix the driver behind crashes, sound loss and screen glitchesA 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.
Best Value
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.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.
Recommended Free Tools
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.
Quick Recap
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.

