ScreenshotNeo

BlogHow-to

Puppeteer Frame.addStyleTag Options Explained

Learn how Puppeteer’s Frame.addStyleTag accepts inline CSS, local files, and stylesheet URLs, and how to target the right frame.

By the ScreenshotNeo team4 October 20266 min read

frame.addStyleTag(options) adds a stylesheet to the specific Puppeteer frame on which you call it. Use content for CSS text, path for a local CSS file, or url for a stylesheet URL. The method returns a handle to the loaded element: a style element for inline or file CSS, and a link element for URL CSS. See Puppeteer’s Frame.addStyleTag API reference and StyleTagOptions.

Choose the stylesheet source

Option Use it for What Puppeteer adds
content CSS text already available in your script A <style> element
path A CSS file on the machine running Node.js A <style> element containing the file’s CSS
url A stylesheet that the browser should load from a URL A <link> element

The options are documented as optional, but the API reference does not specify precedence or validation when multiple sources are supplied. Pass exactly one source option so the intended behavior is clear.

Runnable examples

These examples use the current Puppeteer API shape. Install Puppeteer with npm install puppeteer, then save an example as a JavaScript file and run it with Node.js. Replace the URL or CSS source as needed.

Inline CSS with content

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();
    await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });

    const styleHandle = await page.mainFrame().addStyleTag({
      content: 'body { background: #f5f5f5; color: #222; }'
    });
    console.log(await styleHandle.evaluate((el) => el.tagName)); // STYLE
  } finally {
    await browser.close();
  }
})();

CSS from a local file with path

const puppeteer = require('puppeteer');
const path = require('node:path');

(async () => {
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();
    await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });

    const cssPath = path.resolve(process.cwd(), 'styles', 'capture.css');
    const styleHandle = await page.mainFrame().addStyleTag({ path: cssPath });
    console.log(await styleHandle.evaluate((el) => el.tagName)); // STYLE
  } finally {
    await browser.close();
  }
})();

For example, styles/capture.css could contain:

body { font-family: sans-serif; }
header, footer { display: none !important; }

A relative path is resolved from Node.js’s current working directory, process.cwd(), not automatically from the JavaScript file’s directory or the page URL. Resolving it to an absolute path makes that choice explicit.

Stylesheet URL with url

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();
    await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });

    const linkHandle = await page.mainFrame().addStyleTag({
      url: 'https://example.com/capture.css'
    });
    console.log(await linkHandle.evaluate((el) => el.tagName)); // LINK
  } finally {
    await browser.close();
  }
})();

The URL form asks the browser to load a linked stylesheet. The host must be reachable from the browser, and the resource must be served as usable CSS. Network, redirect, access-control, or stylesheet response problems can prevent the expected styling from appearing.

Target the intended frame

Frame.addStyleTag() affects the frame represented by that Frame object. A stylesheet added to the main frame does not automatically style a child frame. Page.addStyleTag(options) is a shortcut for page.mainFrame().addStyleTag(options); use it when the main frame is the target. See the Page.addStyleTag reference.

// Main frame: either form targets the top-level document.
await page.addStyleTag({ content: 'body { outline: 2px solid teal; }' });
// Equivalent:
await page.mainFrame().addStyleTag({ content: 'body { outline: 2px solid teal; }' });

// Child frame: locate it, then call the method on that frame.
const childFrame = page.frames().find((frame) => frame !== page.mainFrame());
if (!childFrame) {
  throw new Error('Expected a child frame, but none was found');
}
await childFrame.addStyleTag({ content: 'body { background: #fffbe6; }' });

If a page has several child frames, identify the one you need by a stable frame URL or another property of the page rather than assuming the first child is the right target.

Return value and cleanup

The API returns a promise that resolves to an element handle after the element is loaded. The non-URL overload returns a handle typed as ElementHandle<HTMLStyleElement>; the URL overload returns ElementHandle<HTMLLinkElement>. Keep the handle if you need to inspect or remove the injected element:

const styleHandle = await page.mainFrame().addStyleTag({
  content: 'body { background: lavender; }'
});

// Remove this injected style while the frame is still available.
await styleHandle.evaluate((element) => element.remove());

Removing the element reverses that stylesheet’s contribution. It does not undo other page changes or stylesheets.

Common problems and fixes

Symptom Likely cause Fix
CSS file cannot be found A relative path was resolved from an unexpected working directory. Log process.cwd() and use path.resolve(process.cwd(), '...') or provide a verified absolute path.
No visible change The style was added to a different frame, the selector matches nothing, or another rule takes precedence. Confirm the target frame and inspect the rendered document. Test a distinctive rule and use a suitably specific selector; reserve !important for cases where it is necessary.
URL stylesheet does not apply The browser could not load the URL, or the response is not usable CSS. Check the URL and browser network errors, verify the endpoint serves CSS, and ensure the browser can access it without unavailable credentials.
Call rejects or times out The frame navigated or detached while the stylesheet was being added, or a URL load did not complete. Wait until navigation reaches the state your task needs, reacquire the current frame after navigation, and handle failures around the call.
Inline CSS seems malformed JavaScript string escaping or CSS syntax changed the intended text. Use a template literal for multiline CSS and validate braces, quotes, and URLs.

Timing, performance, and reliability

  • Add styles after the target document exists, typically after page.goto() or after the desired frame has attached and navigated.
  • For deterministic screenshots, apply the style before capturing and wait for any page behavior triggered by it to settle.
  • content avoids a stylesheet-file lookup. path reads a local file, while url adds a browser network dependency; choose based on where the CSS is maintained and how it should be delivered.
  • Keep the CSS small and focused for repeated captures. Large style rules can change layout and increase the work required to render the page.
  • Reacquire frame references after navigation if the page replaces a frame. A detached or stale frame cannot receive new styles.

addStyleTag itself has no separate Puppeteer usage charge described in the API reference. Its practical cost is the browser process and any stylesheet I/O your workflow performs. If you are running many captures, reuse a managed browser where appropriate and ensure each task closes pages or browsers it owns.

Or skip the browser setup

For a screenshot without managing Puppeteer or a browser, ScreenshotNeo takes a URL in one API request. 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,
)
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 Bun.write('shot.webp', res);

ScreenshotNeo removes cookie banners, 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, and paid plans start at $5 for 3,000. ScreenshotNeo is a website screenshot API and MCP server by Yorker Media; see ScreenshotNeo for details and the docs for its API options.

Sign up for 1,000 free screenshots a month, with no card required.

FAQ

Can I pass more than one of content, path, and url?

The fields are documented as optional, but the reference does not define precedence for multiple values. Supply one source per call.

Does path refer to a file on the website?

No. It names a file accessible to the Node.js process running Puppeteer. Use url when the stylesheet should be fetched by the browser from a web address.

Does Page.addStyleTag() style every frame?

No. It targets the main frame. Call addStyleTag() on a particular child Frame to style that frame.

Which Puppeteer version should I check?

Use the API reference corresponding to the version installed in your project. Puppeteer documentation pages can display different package versions, and signatures may vary between releases.