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

In Puppeteer, keep your stylesheet in a JavaScript string and inject it immediately before PDF generation with await page.addStyleTag({ content: cssString }). Then call page.pdf(). This avoids creating a temporary .css file while preserving normal CSS, including @page rules, print media queries, colors, and page-break controls.

Complete working example

The following Node.js script creates a page, loads HTML, adds CSS held in memory, and writes a PDF. The example uses Puppeteer 25.12.0 API behavior documented on September 30, 2026.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();

    await page.setContent(`
      <!doctype html>
      <html>
        <head><meta charset="utf-8"></head>
        <body>
          <h1>Invoice 1042</h1>
          <p>Prepared for Example Client</p>
          <table>
            <tr><th>Item</th><th>Amount</th></tr>
            <tr><td>Consulting</td><td>$500</td></tr>
          </table>
        </body>
      </html>`, { waitUntil: 'load' });

    const cssString = `
      @page { size: A4; margin: 18mm; }
      body {
        font: 12pt Arial, sans-serif;
        color: #222;
        line-height: 1.45;
      }
      h1 { color: #165d9c; margin-bottom: 4mm; }
      table { width: 100%; border-collapse: collapse; }
      th, td { border: 1px solid #bbb; padding: 3mm; text-align: left; }
      th { background: #eaf2f8; }
    `;

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

addStyleTag inserts a <style type="text/css"> element containing the supplied string. Add it after the document exists and before page.pdf(); otherwise the PDF can be generated before the rules are attached.

Install Puppeteer and run the script

  1. Create a project and install Puppeteer: npm init -y && npm install puppeteer.
  2. Save the example as make-pdf.js.
  3. Run node make-pdf.js.
  4. Open invoice.pdf in a PDF viewer and check page size, colors, fonts, and page breaks.

The regular Puppeteer package downloads a compatible browser during installation. In a restricted deployment, ensure the runtime has a supported Chromium executable and configure its launch options according to your deployment environment.

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

Two ways to provide CSS as a string

Inject a separate CSS string

page.addStyleTag({ content: cssString }) is the clearest choice when templates and styles are assembled independently. You can build cssString from configuration, a theme, or a database value without writing a file.

Embed the style in the HTML string

const html = `
  <html>
    <head>
      <style>
        @page { size: Letter; margin: 0.7in; }
        body { font-family: Arial, sans-serif; }
      </style>
    </head>
    <body><h1>Report</h1></body>
  </html>`;

await page.setContent(html);
await page.pdf({ path: 'report.pdf', format: 'Letter' });

Both approaches produce inline CSS. Use the embedded form when one template owns its styling; use addStyleTag when you want HTML and CSS to remain separate or when CSS is assembled at runtime.

Print media, backgrounds, and color accuracy

Puppeteer’s PDF method uses the print CSS media type by default. Rules inside @media screen therefore do not normally control the PDF. If the design is intentionally written for screen media, switch before printing:

await page.emulateMediaType('screen');
await page.pdf({ path: 'screen-styled.pdf', printBackground: true });

Use this only when screen rules are what you want. For a print stylesheet, leave the default media type and define print-specific rules with @media print.

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

printBackground defaults to false. Set it to true for colored table headers, banners, background images, and other CSS backgrounds. Printed colors may still differ from the browser window because PDF generation applies print color treatment. Add this rule when exact CSS colors are important:

* {
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

Color adjustment can increase ink usage and does not compensate for missing assets or an incorrect media type, so verify the resulting PDF.

Control paper size, margins, and scaling

There are two sizing mechanisms: CSS @page and Puppeteer’s PDF options. The documented default format is letter, unspecified margins are zero, and preferCSSPageSize defaults to false. Set these values deliberately.

Requirement Configuration Effect
Use a standard paper preset format: 'A4' or format: 'Letter' Puppeteer chooses the paper dimensions.
Use exact dimensions width and height Defines the PDF page size directly.
Use CSS page size preferCSSPageSize: true Lets @page { size: ... } take priority.
Reserve printable space margin: { top, right, bottom, left } Adds explicit PDF margins.

Do not specify conflicting sizing rules casually. For predictable pagination, choose one authority: either a Puppeteer format/dimension or CSS @page with preferCSSPageSize: true. Then set margins explicitly and inspect long tables and headings at page boundaries.

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

Example with explicit PDF options

await page.pdf({
  path: 'statement.pdf',
  format: 'A4',
  margin: {
    top: '18mm',
    right: '18mm',
    bottom: '18mm',
    left: '18mm'
  },
  printBackground: true,
  preferCSSPageSize: false
});

Wait for fonts, images, and other resources

Puppeteer’s documented PDF option waitForFonts is enabled by default. That covers font readiness, not every external image, stylesheet dependency, or web-font failure. When content is loaded from URLs, wait for the resources your document actually needs.

await page.setContent(html, { waitUntil: 'networkidle0' });
await page.evaluate(() => document.fonts.ready);
await page.waitForSelector('#report-ready');
await page.pdf({ path: 'report.pdf', printBackground: true });

For images, use stable absolute URLs or data URLs and confirm they are present before printing:

await page.evaluate(async () => {
  const images = Array.from(document.images);
  await Promise.all(images.map(img => {
    if (img.complete) return Promise.resolve();
    return new Promise(resolve => {
      img.addEventListener('load', resolve, { once: true });
      img.addEventListener('error', resolve, { once: true });
    });
  }));
});

A network-idle wait is not a guarantee that a slow, blocked, or failed image has rendered; inspect the page and handle missing assets explicitly.

Useful print CSS for real documents

@page { size: A4; margin: 16mm 14mm 18mm; }

h1, h2, h3 { break-after: avoid; }
table, figure { break-inside: avoid; }
thead { display: table-header-group; }
tr { break-inside: avoid; }
.page-break { break-before: page; }

@media print {
  .no-print { display: none !important; }
}

These properties reduce awkward splits, but pagination remains content-dependent. Test documents with unusually long paragraphs, large images, and tables that span several pages.

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

cURL, Python, and Node.js alternatives

cURL

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

Python

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)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

These calls use ScreenshotNeo’s website capture API rather than a local Puppeteer renderer. Its API can return PNG, JPEG, WebP, or PDF; see the ScreenshotNeo documentation for request options and PDF-specific parameters.

Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server for developers. Before capture, it accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed as clean shots, and response headers identify the page verdict and billing result. Its MCP tools—take_screenshot, get_page_info, and capture_pdf—work with Claude, Cursor, and other MCP clients. Every plan includes the features; 1,000 shots per month are free with no card, and paid plans start at $5 for 3,000 shots.

To start, create a free account at ScreenshotNeo’s sign-up page.

Troubleshooting checklist

CSS has no effect

  • Verify cssString is not empty and contains valid CSS.
  • Await page.addStyleTag; do not call page.pdf first.
  • Check selectors against the actual HTML and inspect computed styles with page.evaluate.

Screen styling is missing

The PDF uses print media. Remove screen-only assumptions or call await page.emulateMediaType('screen') before printing.

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

Backgrounds or colored headers disappear

Set printBackground: true. If colors still differ, add -webkit-print-color-adjust: exact and confirm the CSS is active in the selected media type.

Pages are unexpectedly scaled

Check for competing @page, format, width, height, and margin settings. Decide whether CSS or PDF options should control page size, then configure preferCSSPageSize accordingly.

Fonts or images are missing

Wait for document.fonts.ready, use networkidle0 where appropriate, and verify external URLs, permissions, and failed requests. Font readiness alone does not prove that every resource loaded.

The process hangs or leaves Chromium running

Use try...finally and always call browser.close(). Set an application-level timeout around navigation and resource waits so a failed remote dependency cannot hold a worker indefinitely.

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

Operational and cost considerations

For server workloads, reuse a browser process when safe and create a new page per document; launching Chromium for every small PDF adds startup overhead. Bound concurrent pages to the memory available to your service. Keep untrusted HTML and CSS isolated from sensitive pages, and validate any user-controlled URLs or headers before navigation.

CSS stored in memory avoids temporary-file cleanup, but very large templates still consume browser memory. If a generated PDF is a contractual document, retain the HTML, CSS version, input data, and Puppeteer version used to create it so a later reproduction is possible.

Frequently Asked Questions

Does this require writing a CSS file first?

No. Pass the in-memory value to page.addStyleTag({ content: cssString }), or put a <style> element inside the HTML string.

Which media type does Puppeteer use for PDFs?

page.pdf() uses print media by default. Call page.emulateMediaType('screen') when the PDF must use screen rules.

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

Why are CSS backgrounds absent?

The documented default for printBackground is false; set it to true.

Should CSS or Puppeteer control paper size?

Choose one deliberately. Use preferCSSPageSize: true when @page should take priority; otherwise use Puppeteer’s format or dimensions.

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.