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.

For a webpage that is already published at a URL, the most direct Node.js method is to use Puppeteer: open a browser page, navigate to the URL, and save the result with page.pdf(). The example below writes an A4 PDF and closes the browser even if navigation or PDF generation fails. Puppeteer prints using print CSS by default; if you need the page’s screen styling, switch the page to screen media before creating the PDF.

Convert a URL to PDF with Puppeteer

Install Puppeteer in your Node.js project, then create a script such as url-to-pdf.js. Puppeteer’s documented flow is to launch the browser, create a page, navigate to the URL, call page.pdf(), and close the browser. Its guide demonstrates waitUntil: 'networkidle2' for navigation.

npm install puppeteer
const puppeteer = require('puppeteer');

async function saveUrlAsPdf(url, outputPath) {
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();
    await page.goto(url, { waitUntil: 'networkidle2' });
    await page.pdf({ path: outputPath, format: 'A4' });
  } finally {
    await browser.close();
  }
}

saveUrlAsPdf('https://example.com', './page.pdf')
  .catch((error) => {
    console.error('PDF generation failed:', error);
    process.exitCode = 1;
  });

Run the script from your project directory:

node url-to-pdf.js

The output path is relative to the current working directory, so ./page.pdf appears in the directory from which you run the command. You can instead pass an absolute path. The finally block is a cleanup pattern: it asks Puppeteer to close the browser whether navigation and PDF generation succeed or throw an error.

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

Choose the right readiness condition

networkidle2 waits for network activity to become idle according to Puppeteer’s navigation condition. It is a useful starting point, not a universal guarantee that every application has finished rendering. A page may continue polling, render content after an API response, or need a specific UI element before it is ready. For those sites, use an application-specific readiness check, such as waiting for a known selector, rather than assuming one navigation setting fits every URL. The Puppeteer guide says PDF generation waits for fonts by default, but that does not establish that all page-specific JavaScript or asynchronous content is complete.

Decide whether the PDF should use print or screen styling

Puppeteer’s page.pdf() renders with print CSS by default. Print styles may hide navigation, change colors, or reflow content to suit paper. That is often desirable for a document, but not if the PDF should closely match the browser’s screen presentation.

await page.emulateMediaType('screen');
await page.pdf({ path: './page.pdf', format: 'A4' });

Call emulateMediaType('screen') after navigating and before generating the PDF when screen media is the intended output. Playwright has the equivalent media emulation call, page.emulateMedia({ media: 'screen' }).

Preserve print colors when needed

Print rendering may alter colors. Puppeteer documents the CSS property -webkit-print-color-adjust as a way to request exact color rendering. For example, a page stylesheet can use:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
@media print {
  html {
    -webkit-print-color-adjust: exact;
  }
}

Color output can also depend on the page’s styles and the browser’s print behavior, so inspect the generated PDF for the target pages and styles.

Set page size and choose file or memory output

Puppeteer’s PDF options accept a paper format, as well as explicit width and height dimensions. When format is present, it takes priority over width and height. The documented default format is Letter, so specify the format if your workflow expects a different paper size.

await page.pdf({ path: './page.pdf', format: 'A4' });

To specify dimensions instead, omit format and set width and height using Puppeteer’s supported dimension values. Avoid supplying both a format and dimensions expecting the dimensions to override the format.

Providing path writes the PDF to a file. Puppeteer’s API also documents a returned Uint8Array, which can be useful when your application wants to process or send the PDF without first choosing a file path. Playwright’s PDF API returns a PDF buffer. Select output handling based on what the surrounding application needs, rather than assuming a file is always necessary.

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

Use Playwright if it fits your existing Node.js project

Playwright also provides a browser-page PDF API. It is a comparable choice when the application already uses Playwright or when its deployment setup fits your project. A minimal version is:

const { chromium } = require('playwright');

async function saveUrlAsPdf(url, outputPath) {
  const browser = await chromium.launch();
  try {
    const page = await browser.newPage();
    await page.goto(url, { waitUntil: 'networkidle' });
    const pdf = await page.pdf({ format: 'A4' });
    const fs = require('node:fs/promises');
    await fs.writeFile(outputPath, pdf);
  } finally {
    await browser.close();
  }
}

saveUrlAsPdf('https://example.com', './page.pdf').catch((error) => {
  console.error('PDF generation failed:', error);
  process.exitCode = 1;
});

Install Playwright and its supported browser before running a script that launches Chromium; consult Playwright’s installation instructions for the runtime and browser setup matching your environment. For screen styling, use await page.emulateMedia({ media: 'screen' }) before page.pdf(). Puppeteer and Playwright both render a browser page, but the references do not establish a general performance winner. Prefer the library already used by your application, and verify that its browser setup works in your deployment environment.

When PDFKit is the better tool

PDFKit is a different kind of solution. It creates PDF content programmatically using a PDFDocument and a writable stream; it is not the browser-print approach for reproducing an already rendered web page at a URL. Choose a browser automation library when you need to render the page as a browser sees it. Consider PDFKit when you want to construct the PDF’s text, shapes, and layout directly in application code rather than print an existing webpage.

Or skip the browser setup

If you need a PDF from a URL without managing a browser runtime, ScreenshotNeo provides a screenshot and PDF API. This cURL request asks for a PDF of the example URL; see the ScreenshotNeo API documentation for PDF parameters and response handling.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -G "https://api.screenshotneo.com/v1/shot" 
  -d access_key=YOUR_API_KEY 
  --data-urlencode url=https://stripe.com 
  -d format=pdf 
  -o page.pdf

ScreenshotNeo accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each of those steps can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers indicate the page verdict and billing status. It also offers an MCP server with take_screenshot, get_page_info, and capture_pdf tools for AI agents and MCP clients. The free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots.

Sign up for ScreenshotNeo’s free plan to try 1,000 screenshots a month without a card.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshoot common PDF-generation problems

The script cannot launch the browser

Check that Puppeteer is installed in the project where the script runs and that the runtime can launch its browser. Browser installation and runtime compatibility can vary with package versions and deployment environments. If using Playwright, install the browser supported by your Playwright setup. Verify locally before moving the same script into a server or container.

The PDF is blank or misses dynamic content

Navigation completing does not necessarily mean a single-page application or delayed widget has finished drawing. Try a wait condition tied to the content you need, or an application-specific delay if the site provides no reliable selector. Do not assume that waiting for fonts also waits for arbitrary application requests.

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

The PDF looks different from the page on screen

Check whether print CSS is changing the layout. If the output needs screen styles, emulate screen media before calling page.pdf(). If only colors differ, review print-specific styles and whether -webkit-print-color-adjust: exact is appropriate.

The paper size is wrong

Set format explicitly, for example A4, instead of relying on Puppeteer’s documented Letter default. Remember that format takes precedence over width and height.

The output file is not where expected

A relative Puppeteer path resolves from the process’s current working directory, which may differ from the script’s directory. Run the command from the intended directory or pass an absolute output path.

Performance, reliability, and cost considerations

Local Puppeteer or Playwright gives your Node.js process direct control over navigation and rendering, but your application must provide a compatible browser runtime and handle the work of loading the target page. The time taken depends on the target site, its assets, and the readiness condition; the documentation cited here does not establish a universal conversion time or performance ranking. For a production workflow, use an appropriate timeout, close the browser in cleanup logic, and test pages with the same runtime and network conditions as deployment.

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.

These browser libraries do not have a per-capture price stated in the cited documentation. Their practical cost depends on the environment where your code runs and the resources needed for its browser processes. If you prefer a managed URL-to-PDF request over operating a browser yourself, ScreenshotNeo’s listed plans are Free (1,000 shots/month), Starter ($5 for 3,000), Growth ($15 for 15,000), Pro ($39 for 60,000), Scale ($99 for 250,000), and Business ($249 for 1,000,000); yearly billing gives two months free, and every feature is available on every plan.

Frequently Asked Questions

Does Puppeteer wait for web fonts before generating a PDF?

Yes. Puppeteer’s PDF generation guide says it waits for fonts to load by default.

Can I return the PDF from an API endpoint instead of saving it to disk?

Puppeteer documents a returned Uint8Array from PDF generation, and Playwright documents a returned PDF buffer. Use the API’s returned data when you want to send or process the PDF in memory.

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.