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 Puppeteer’s page.addScriptTag({ url }) to inject a remote JavaScript file into the page, wait for your application to signal that rendering is complete, and only then call page.pdf(). Puppeteer prints with print CSS by default, so explicitly choose print or screen media for predictable output.

Minimal working example

Install Puppeteer in your Node.js project:

npm install puppeteer

This complete ES module loads HTML, inserts a script from a URL, waits for an application-owned readiness flag, and writes an A4 PDF:

import puppeteer from 'puppeteer';

const scriptUrl = 'https://example.com/app.js';

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.setContent(`<!doctype html>
<html>
  <head><meta charset="utf-8"></head>
  <body><main id="app"></main></body>
</html>`);

  await page.addScriptTag({ url: scriptUrl });

  // app.js must set this after its asynchronous work is finished.
  await page.waitForFunction(() => window.pdfContentReady === true);

  await page.pdf({
    path: 'output.pdf',
    format: 'A4',
    printBackground: true
  });
} finally {
  await browser.close();
}

Replace the URL and readiness contract with your own trusted script. addScriptTag() resolves to a handle for the inserted <script> element. The script runs in the current page or frame context; it does not magically become a Node.js module.

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

How the loading and PDF sequence works

1. Start with either a real page or generated HTML

If the target website already includes the JavaScript, navigate to it instead of injecting the same file a second time:

#1 Best Overall
Sale
Philips 24 Inch Computer Monitor FHD 100Hz VA VESA Flicker-Free, 241V8LB
  • CRISP CLARITY: This 23.8″ Philips V line monitor delivers crisp Full HD 1920x1080 visuals. Enjoy movies, shows and videos with remarkable detail
  • INCREDIBLE CONTRAST: The VA panel produces brighter whites and deeper blacks. You get true-to-life images and more gradients with 16.7 million colors
  • THE PERFECT VIEW: The 178/178 degree extra wide viewing angle prevents the shifting of colors when viewed from an offset angle, so you always get consistent colors
  • WORK SEAMLESSLY: This sleek monitor is virtually bezel-free on three sides, so the screen looks even bigger for the viewer. This minimalistic design also allows for seamless multi-monitor setups that enhance your workflow and boost productivity
  • A BETTER READING EXPERIENCE: For busy office workers, EasyRead mode provides a more paper-like experience for when viewing lengthy documents
await page.goto('https://example.com/report', { waitUntil: 'networkidle2' });
await page.waitForSelector('#report-ready');
await page.pdf({ path: 'report.pdf', format: 'A4' });

Puppeteer’s PDF guide uses navigation followed by page.pdf(). networkidle2 only describes network activity; polling, delayed rendering, or client-side work can continue after the network becomes quiet. Wait for a selector, global flag, or other signal that represents finished business content.

2. Inject a URL script into constructed HTML

For an HTML string assembled in Node.js, call:

await page.addScriptTag({ url: 'https://cdn.example.com/report.js' });

The API also accepts inline content, a local path, and a type such as module. A relative path is resolved from Node’s current working directory. See the Page.addScriptTag() API and FrameAddScriptTagOptions reference for the exact options supported by your installed version.

3. Define a reliable ready signal

Your application should set a flag or render a marker only after its asynchronous work is complete:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
// app.js, running in the page
(async () => {
  const response = await fetch('/api/report');
  const data = await response.json();
  document.querySelector('#app').textContent = data.title;
  window.pdfContentReady = true;
})();

Alternatively, wait for a meaningful DOM marker:

await page.waitForSelector('[data-pdf-ready="true"]', { timeout: 30000 });

Do not treat Puppeteer’s font wait as proof that charts, API requests, or arbitrary JavaScript have completed. The official guide documents font waiting, not a universal application-readiness guarantee.

Rank #2
Philips 22 Inch Computer Monitor FHD 100Hz VA VESA Flicker-Free, 221V8LB
  • CRISP CLARITY: This 22 inch class (21.5″ viewable) Philips V line monitor delivers crisp Full HD 1920x1080 visuals. Enjoy movies, shows and videos with remarkable detail
  • 100HZ FAST REFRESH RATE: 100Hz brings your favorite movies and video games to life. Stream, binge, and play effortlessly
  • SMOOTH ACTION WITH ADAPTIVE-SYNC: Adaptive-Sync technology ensures fluid action sequences and rapid response time. Every frame will be rendered smoothly with crystal clarity and without stutter
  • INCREDIBLE CONTRAST: The VA panel produces brighter whites and deeper blacks. You get true-to-life images and more gradients with 16.7 million colors
  • THE PERFECT VIEW: The 178/178 degree extra wide viewing angle prevents the shifting of colors when viewed from an offset angle, so you always get consistent colors

Choosing print or screen styling

page.pdf() uses print CSS media by default. If the design is written for the screen, switch media before printing:

await page.emulateMediaType('screen');
const pdf = await page.pdf({
  format: 'A4',
  printBackground: true
});

Use print media when you have dedicated @media print rules for page breaks, margins, and omitted navigation. Use screen media when the on-screen layout is the intended artifact. Puppeteer also adjusts colors for printing by default; CSS -webkit-print-color-adjust: exact can preserve specified colors where appropriate. Review the resulting PDF for clipped content, unexpected breaks, missing backgrounds, and unavailable fonts.

The Puppeteer PDF generation guide, Page.pdf() reference, and PDFOptions interface describe current options such as paper format, margins, landscape mode, page ranges, headers, footers, and background printing.

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

Common production patterns

Passing data to the page

Keep data in a JSON-safe object and expose it deliberately rather than interpolating unescaped values into JavaScript source:

Rank #3
Sale
Dell 24 Monitor - SE2426H - 23.8-inch FHD (1920x1080) 144Hz 1ms Display, in-Plane Switching (IPS) Technology, AMD FreeSync™, TÜV 3-Star 2X HDMI, Tilt
  • Clear visuals. Fluid motion: A 144Hz refresh rate and 1ms MPRT deliver smooth, tear‑free motion across work, gaming, and streaming for clearer, more fluid viewing.
  • Eye comfort: TÜV Rheinland 3‑star* certification reduces harmful blue light while preserving stunning color quality without compromise. *TÜV Rheinland 3-star eye comfort certification.
  • Wide viewing angle: Get consistent views across a wide 178° /178° viewing angle.
  • In-Plane Switching (IPS): See excellent color accuracy and consistency across wide viewing angles with In-plane Switching (IPS) technology.
  • Ultra-thin bezels: Maximize your viewing experience with thin bezels.
await page.evaluate((report) => {
  window.reportInput = report;
}, reportObject);
await page.addScriptTag({ url: scriptUrl });
await page.waitForFunction(() => window.pdfContentReady === true);

Modules and dependencies

If the remote file is an ES module, request the module type when supported by your Puppeteer version:

await page.addScriptTag({
  url: 'https://cdn.example.com/report.mjs',
  type: 'module'
});

Module imports still obey browser URL, CORS, CSP, and network rules. A script that works in a normal browser can fail in a restricted server environment.

Returning a buffer instead of writing a file

const pdfBuffer = await page.pdf({ format: 'A4', printBackground: true });
// Store pdfBuffer in object storage or send it in an HTTP response.

When using an HTTP server, set the response type to application/pdf and avoid logging the binary buffer.

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

Waiting, timeouts, and failure diagnosis

Symptom Likely cause Fix
PDF contains the empty shell PDF generation ran before the script finished rendering. Add an application-owned flag or selector and await it before page.pdf().
addScriptTag rejects DNS, TLS, HTTP status, redirect, CSP, or an inaccessible URL. Open the URL from the rendering host, use HTTPS, inspect page console and request failures, and verify the URL is allowed by the page’s policy.
Wait times out The readiness flag is never set, an API call failed, or the selector differs from the generated DOM. Capture console and page errors, inspect the DOM, and make the application set a failure state as well as a success state.
Styles look wrong Print media is active, backgrounds are disabled, or a font has not loaded. Choose emulateMediaType('screen') when needed, enable printBackground, and wait for the page’s font/content condition.
Duplicate charts or event handlers The page already loaded the script and it was injected again. Navigate to the existing page or detect the existing script before adding another copy.
Works locally, fails in deployment Different Chromium, Puppeteer, certificates, DNS, proxy, or outbound firewall rules. Pin Puppeteer, run the same browser build in each environment, and test outbound access from the worker.

Set explicit timeouts for navigation and readiness so jobs fail predictably:

Rank #4
Samsung 27" Essential S3 (S36GD) Series FHD 1800R Curved Computer Monitor
  • CURVED FOR ENHANCED ENGAGEMENT: An immersive viewing experience with a curved monitor that wraps more closely around your field of vision; It creates a wider view, enhancing depth perception and minimizing peripheral distraction
  • SMOOTH PERFORMANCE FOR SEAMLESS CONTENT: Stay in the action when playing games, watching videos, or working on creative projects; The 100Hz refresh rate reduces lag and motion blur so you don't miss a thing in fast-paced moments¹
  • MORE GAMING POWER: Gain the edge with optimizable game settings; Color and image contrast can be adjusted to see scenes more vividly and spot enemies hiding in the dark; Game Mode adjusts any game to fill the screen so you can view every detail²
  • KEEP IT EASY ON THE EYES: Care for your eyes and stay comfortable, even during long sessions; Advanced eye comfort technology certified by TÜV reduces eye strain by minimizing blue light and reducing irritating screen flicker²
  • INCREASED VERSATILITY: Connect to more; Plug devices straight into your monitor for increased flexibility, making your computing environment even more convenient
page.setDefaultNavigationTimeout(60000);
page.setDefaultTimeout(30000);

For diagnostics, attach listeners before loading the script:

page.on('console', msg => console.log('[browser]', msg.type(), msg.text()));
page.on('pageerror', error => console.error('[page error]', error));
page.on('requestfailed', request => {
  console.error('[request failed]', request.url(), request.failure()?.errorText);
});

Security boundaries and safer deployment

A URL-controlled renderer fetches and executes code selected by a URL. Puppeteer’s security policy states: “Puppeteer provides powerful capabilities for browser installation, automation, and inspection, and it is the responsibility of the calling code to ensure these are used safely and as intended.” Treat script URLs, page input, cookies, headers, and outbound requests as part of your application’s trust boundary.

  • Do not let untrusted users submit arbitrary script URLs.
  • Allow only approved HTTPS hosts and validate redirects against the same policy.
  • Run rendering workers without credentials for internal services, cloud metadata, or production databases.
  • Use network egress controls to limit destinations and prevent access to private address ranges.
  • Keep sensitive cookies and authorization headers out of pages that load third-party code.
  • Do not copy --no-sandbox into production without reviewing the Chromium and hosting environment.

Puppeteer supports request interception for inspecting or aborting resource requests. A Chrome Developers example demonstrates an allowlist pattern, but it is an older article; verify the snippet against your installed Puppeteer version. Interception is one control, not a complete defense against hostile pages, redirects, or browser vulnerabilities.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Version compatibility and operational notes

The official PDF guide is surfaced as Puppeteer 25.12.0, while the addScriptTag references are surfaced as 25.10.0. That does not establish a behavioral conflict, but signatures and browser behavior can change. Pin a Puppeteer version, read documentation matching that version, and test the exact Chromium build used by your workers.

Best Value
Sale
Sceptre New 22-Inch Gaming Monitor, FHD 1080p, Up to 144Hz, HDMI, DisplayPort, Built-in Speakers, Machine Black (E225W-FW144 Series, 2026)
  • 【INTEGRATED SPEAKERS】Whether you're at work or in the midst of an intense gaming session, our built-in speakers provide rich and seamless audio, all while keeping your desk clutter-free.
  • 【EASY ON THE EYES】 Protect your eyes and enhance your comfort with Blue-Light Shift technology. This feature reduces harmful blue light emissions from your screen, helping to alleviate eye strain during long hours of use and promoting healthier viewing habits.
  • 【WIDEN YOUR PERSPECTIVE】Our sleek minimal bezel design ensures undivided attention. The nearly bezel-free display seamlessly connects in a dual monitor arrangement, delivering an unobstructed view that lets you focus on more at once, completely distraction-free.

For throughput, reuse a browser process when appropriate, create isolated pages per job, close pages in a finally block, and impose limits on page size, script execution time, and concurrent jobs. Cache immutable script assets where your security policy permits, but do not cache personalized pages or authorization-bearing responses accidentally. Measure your own queue time, browser startup time, rendering time, and PDF size; the cited Puppeteer documentation does not provide a universal performance or cost benchmark.

Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server for developers. Its endpoint can return PNG, JPEG, WebP, or PDF output after rendering a URL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo documentation for output and rendering parameters. The service accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

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.

The free plan includes 1,000 screenshots per month without a card. Paid plans start at $5 for 3,000 shots; every plan includes all features. Options include full-page lazy-image loading, CSS-selector element capture, dark mode, device presets, retina scale, PDF paper and page controls, custom CSS and JavaScript, click actions, waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, selectable-TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture of 100 URLs per call, a usage API, and an OpenAPI specification. Common screenshot-API parameter names are also accepted to ease migration.

Create a free ScreenshotNeo account to use the 1,000 monthly screenshots with no card, or choose a paid plan starting at $5 for 3,000.

Frequently Asked Questions

Can I inject a script after calling page.pdf()?

No. PDF generation captures the current rendered state; add the script and await its ready condition first.

Is networkidle2 enough for a JavaScript application?

Not necessarily. It is a network-idle heuristic, so use an application-owned selector or flag when rendering completion matters.

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

Does addScriptTag bypass a page’s security policy?

No. Browser policies, network access, redirects, and the page’s execution environment still apply; validate URLs and isolate the renderer.

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.