How to Add a Script to a Frame in Puppeteer
Target the right Puppeteer frame, inject inline or external JavaScript, and handle frame selection, navigation, and common errors.
To add a script to an iframe in Puppeteer, find the intended Frame and call await frame.addScriptTag(...). For a script in the top-level document, await page.addScriptTag(...) is a shortcut for adding it to the page’s main frame; it does not target an arbitrary iframe.
This guide uses Puppeteer’s JavaScript API. The examples assume you already have a Puppeteer Page and that the target frame belongs to it. See the official Frame API, script-tag options, Frame.evaluate(), and Page.addScriptTag() documentation.
1. Choose the frame and injection method
A page can contain a main frame and nested child frames. First decide where the script must run, then select that frame. Choose an injection method based on where the code lives and whether you need a script element in the document.
| Goal | Method |
|---|---|
| Add inline JavaScript to a selected frame | frame.addScriptTag({ content }) |
| Load a hosted JavaScript file into a selected frame | frame.addScriptTag({ url }) |
| Load a local JavaScript file into a selected frame | frame.addScriptTag({ path }) |
| Run a function in the selected frame without adding a script element | frame.evaluate(fn) |
| Add a script to the top-level document | page.addScriptTag(options) |
Use a stable, page-specific selector condition such as a known frame URL or name. Avoid selecting an arbitrary frame by array index: frame order can change as frames attach, navigate, or detach.
2. Runnable example: add inline script to an iframe
This complete example launches Chromium, opens a page with a child frame, finds the frame by its URL, injects inline code, checks the result in that frame, and closes the browser even if an error occurs.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.setContent(`
<iframe src="https://example.com/embedded/widget"></iframe>
`);
// Wait for the frame to appear, then select it with a page-specific condition.
await page.waitForFrame(frame =>
frame.url().includes('/embedded/widget')
);
const frame = page.frames().find(frame =>
frame.url().includes('/embedded/widget')
);
if (!frame) {
throw new Error('Target frame was not found');
}
await frame.addScriptTag({
content: 'window.exampleFlag = true;',
});
const flag = await frame.evaluate(() => window.exampleFlag);
console.log('Script ran in target frame:', flag);
} finally {
await browser.close();
}
})().catch(error => {
console.error(error);
process.exitCode = 1;
});
page.waitForFrame() is available in current Puppeteer releases. If your installed version does not provide it, wait for the page’s expected iframe condition with your own polling or a frame-attachment listener, then query page.frames(). Do not keep using a frame reference after its iframe has been replaced or detached; locate the new frame after the page updates.
The URL in this example is illustrative. For a real page, wait for and match the iframe source or another stable property that uniquely identifies the intended frame.
3. Select the frame reliably
page.frames() returns the frames currently attached to a page, including the main frame and child frames. For known frame URLs, a URL predicate is often enough:
const frame = page.frames().find(frame =>
frame.url().includes('/embedded/')
);
if (!frame) {
throw new Error('Target frame was not found');
}
await frame.addScriptTag({ content: 'window.exampleFlag = true;' });
When you need to inspect the frame tree, start from the main frame and walk its children:
function findFrameByUrl(frame, fragment) {
if (frame.url().includes(fragment)) return frame;
for (const child of frame.childFrames()) {
const match = findFrameByUrl(child, fragment);
if (match) return match;
}
return null;
}
const frame = findFrameByUrl(page.mainFrame(), '/embedded/');
if (!frame) throw new Error('Target frame was not found');
You can also inspect a frame’s associated element. The Puppeteer Frame API documents using frame.frameElement() to obtain the frame element handle; the element’s attributes can help distinguish siblings that share similar URLs.
const frame = page.frames().find(frame => frame.url().includes('/widget'));
if (!frame) throw new Error('Target frame was not found');
const element = await frame.frameElement();
const name = await element.evaluate(el => el.getAttribute('name'));
console.log({ url: frame.url(), name });
Frame URLs may be blank or temporary during initial attachment, and a frame may navigate after selection. Wait for the target’s meaningful URL or content before injecting. If the page removes and recreates the iframe, select the replacement instead of reusing the old reference.
4. Add inline, hosted, or local scripts
Inline content
Pass JavaScript source as content. This adds a script element whose text is the provided code.
await frame.addScriptTag({
content: 'window.captureReady = true;',
});
Hosted script URL
Use url to load a script from a URL:
await frame.addScriptTag({
url: 'https://example.test/script.js',
});
The browser must be able to load the URL from the page context. Check network access, redirects, and the target site’s content security policy if it fails.
Local script file
Use path for a file on the machine running Puppeteer. Relative paths resolve from Node.js process.cwd(), which is the process’s current working directory, not necessarily the directory containing your source file.
await frame.addScriptTag({
path: './scripts/prepare-frame.js',
});
ES modules and script element options
The documented options are content, id, path, type, and url. Set type: 'module' when loading module code. You can set id to identify the inserted element later.
const scriptHandle = await frame.addScriptTag({
type: 'module',
content: 'window.moduleReady = true;',
id: 'puppeteer-injected-module',
});
console.log(await scriptHandle.evaluate(el => el.id));
The call returns a promise for a handle to the inserted script element. Choose a module only when the code needs module loading semantics; a classic script is the default when no module type is requested.
5. Run code directly with Frame.evaluate()
If the goal is to execute a function in the frame and you do not need to add a <script> element, use frame.evaluate(). It runs in the selected frame’s JavaScript context.
const title = await frame.evaluate(() => document.title);
console.log(title);
You can pass serializable arguments to the function:
const selector = '.status';
const text = await frame.evaluate(selector => {
return document.querySelector(selector)?.textContent?.trim() ?? null;
}, selector);
console.log(text);
Values returned from evaluate() must be serializable. DOM nodes and other browser objects should be handled through element handles or evaluated into plain values. Evaluation in one frame does not automatically execute in its child frames; select and evaluate against the frame that owns the document you need.
6. Main frame versus iframe
page.addScriptTag(options) is documented as a shortcut for page.mainFrame().addScriptTag(options). Use it for the top-level page:
await page.addScriptTag({ content: 'window.topLevelReady = true;' });
For an iframe, call the method on that iframe’s Frame:
const frame = page.frames().find(frame => frame.url().includes('/embedded/'));
if (!frame) throw new Error('Target frame was not found');
await frame.addScriptTag({ content: 'window.iframeReady = true;' });
Cross-origin iframe content is isolated from the parent page’s JavaScript, but Puppeteer’s frame-scoped APIs address the selected frame directly. Do not use parent-page DOM selectors or page.evaluate() and assume they will run inside a child frame.
7. Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Frame not found | The iframe has not attached yet, the URL predicate is too broad or too narrow, or the page has replaced it. | Wait for the expected frame state; inspect page.frames(), each URL, and frame element attributes. Re-select after replacement. |
| Execution context was destroyed | The selected frame navigated while the operation was running. | Wait for the navigation to finish, then reacquire the frame and retry the operation when appropriate. |
| Cannot find script file | A relative path was resolved from a different current working directory than expected. |
Use an absolute path or build one from a known directory; verify the file exists before calling Puppeteer. |
| Hosted script fails to load | Network failure, redirect or resource policy, or a content security policy restriction. | Check browser network errors and response status; use an allowed URL or inline/local code where suitable. |
| Code appears to run in the wrong document | page.addScriptTag() was used, which targets the main frame. |
Find the child Frame and call frame.addScriptTag(). |
| Script runs but expected value is missing | The code ran before the frame application initialized, threw an exception, or wrote to a different frame context. | Wait for a selector or app-ready condition in the selected frame, inspect console errors, and verify the result with frame.evaluate(). |
| Script is inserted more than once | Automation retries injection or reruns the setup step. | Use a stable id and check for an existing script before inserting, or make the injected code safe to run more than once. |
8. Performance, reliability, and cost
Frame lookup is inexpensive for a page with a modest frame tree, but repeatedly scanning it in a hot loop is unnecessary. Find the target once per stable page state, then reacquire after navigation or replacement. Hosted scripts add a network dependency and can make runs slower or less predictable than inline or local code. A local path avoids fetching the script from the page but still requires the file to exist on the Puppeteer machine.
For reliable automation, make frame selection specific, wait for the condition that means the target is ready, handle navigation and detachment, and make retries safe. Do not assume injecting the same code twice is harmless. Browser automation cost depends on your execution environment and run volume; Puppeteer itself has no ScreenshotNeo billing relationship.
Or skip the browser setup
If your goal is to get a clean screenshot of a page rather than run custom JavaScript inside a particular frame, ScreenshotNeo can capture a URL through one API request. It is a website screenshot API and MCP server for developers. See the ScreenshotNeo 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,
)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before the shot; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up free and get 1,000 screenshots a month with no card.
FAQ
Does adding a script to a frame affect its child frames?
No. The script runs in the selected frame’s context. Select a child frame separately if it needs the same code.
Can I add a script before the iframe loads?
The target frame must exist and be available for Puppeteer to operate on it. Wait for attachment and, when necessary, for its navigation or application readiness before injecting.
Should I use addScriptTag() or evaluate()?
Use addScriptTag() when you need to insert inline, hosted, or local script code as a script element. Use evaluate() for a direct function call in the selected frame context.
Can I use Page.addScriptTag() for an iframe?
No. It targets the main frame. Find the iframe’s Puppeteer Frame and call frame.addScriptTag().


