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.

Use Playwright’s page.pdf() method instead of window.print(). It creates a PDF buffer or writes a file directly, so no user-facing print dialog opens. By default, the PDF uses print CSS; call page.emulateMedia({ media: 'screen' }) first when you need the page’s screen styling.

Generate a PDF directly with page.pdf()

The smallest working Node.js example launches Chromium, navigates to a URL, and saves the generated document:

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

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

  await page.goto('https://example.com', { waitUntil: 'load' });
  await page.pdf({ path: 'page.pdf', format: 'A4' });

  await browser.close();
})();

path is optional. Without it, page.pdf() returns a PDF buffer that you can send from an HTTP route, attach to a job, or store in object storage. A relative path is resolved from the process working directory. Use Chromium for the PDF workflow and install the browser binary that matches your Playwright package:

npm install playwright
npx playwright install chromium

Keep browser shutdown in a finally block in production so a navigation or PDF error does not leave processes running:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const { chromium } = require('playwright');

async function makePdf(url, outputPath) {
  const browser = await chromium.launch();
  try {
    const page = await browser.newPage();
    await page.goto(url, { waitUntil: 'load', timeout: 60000 });
    await page.pdf({ path: outputPath, format: 'A4', printBackground: true });
  } finally {
    await browser.close();
  }
}

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

Choose the PDF settings that match the document

Pass the options that describe the paper and the content you need. The current Page API reference is authoritative for defaults and version-specific additions.

Option Use it for Important detail
path Saving a file Omit it to receive a buffer.
format Standard paper such as A4 or Letter Choose one format, or use explicit dimensions.
width, height Custom paper size Include units in the value when using dimensions.
margin Controlling printable whitespace Set top, right, bottom and left margins independently.
pageRanges Exporting selected pages Use the documented page-range syntax, such as a single page or a range.
printBackground Keeping background colors and images Enable it when the design depends on backgrounds; otherwise they may be omitted.
preferCSSPageSize Honoring CSS @page dimensions Useful when the site defines its own page size.
scale Fitting content without changing CSS Adjust the rendered scale carefully because it changes text and spacing.
displayHeaderFooter Adding generated headers or footers Use with the header and footer template options.
headerTemplate, footerTemplate Page numbers, dates or labels Templates support Playwright’s documented page-number and date placeholders.

For example, this export keeps backgrounds, adds margins, and prints only pages 1 through 3:

await page.pdf({
  path: 'invoice.pdf',
  format: 'A4',
  margin: { top: '18mm', right: '14mm', bottom: '18mm', left: '14mm' },
  printBackground: true,
  pageRanges: '1-3'
});

Do not combine a paper format and a CSS page size casually. If the document contains an @page rule, decide whether the browser’s format or the stylesheet should win, then set preferCSSPageSize accordingly.

Print CSS or screen CSS?

page.pdf() uses print media by default. That is normally what you want for a document: navigation can disappear, columns can reflow, and print-only elements can appear. To preserve the appearance used in a browser window, emulate screen media before generating the PDF:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await page.goto('https://example.com', { waitUntil: 'load' });
await page.emulateMedia({ media: 'screen' });
await page.pdf({ path: 'screen-styled.pdf', format: 'A4', printBackground: true });

Pages often need explicit print rules. A typical stylesheet hides interactive controls and avoids splitting a card across pages:

@media print {
  .site-nav,
  .cookie-settings,
  .share-buttons {
    display: none !important;
  }

  .card {
    break-inside: avoid;
  }
}

@page {
  margin: 16mm;
}

Printed colors can be adjusted by the browser. If exact colors are essential, consider -webkit-print-color-adjust in the page’s print stylesheet and verify the resulting PDF rather than assuming screen colors will be identical.

Wait for the page’s real content before printing

Navigation completion is not the same as application readiness. A page may still be rendering a chart, loading fonts, inserting lazy images, or waiting for an API response after goto() resolves. There is no universal wait condition that works for every site.

Use an application signal

Prefer a selector or state that means the content is ready:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await page.goto('https://example.com/report', { waitUntil: 'domcontentloaded' });
await page.locator('[data-report-ready="true"]').waitFor({ state: 'visible', timeout: 30000 });
await page.pdf({ path: 'report.pdf', format: 'A4' });

Wait for fonts and images when they affect layout

await page.evaluate(async () => {
  if (document.fonts) await document.fonts.ready;
  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 fixed delay can help with a known animation, but it is less reliable than waiting for the application’s own ready signal. If you use waitUntil: 'networkidle', treat it as a site-specific choice: analytics, polling and long-lived connections can prevent a stable idle point.

Return a buffer instead of writing a file

When a web service should stream the PDF to a caller, omit path and set the response headers yourself:

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

// Express example
res.set({
  'Content-Type': 'application/pdf',
  'Content-Disposition': 'attachment; filename="page.pdf"'
});
res.send(pdf);

For large documents, write the buffer to storage immediately and avoid retaining many PDFs in memory at once. Reuse a controlled browser process or a small pool for jobs, but create an isolated page or browser context for each URL so cookies and page state do not leak between requests.

Headers, footers and page design

Generated headers and footers are separate from the page’s DOM. Enable them explicitly and use the documented template placeholders:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await page.pdf({
  path: 'manual.pdf',
  format: 'A4',
  displayHeaderFooter: true,
  headerTemplate: '<div style="font-size:9px;width:100%;text-align:center">Internal manual</div>',
  footerTemplate: '<div style="font-size:9px;width:100%;text-align:center">Page <span class="pageNumber"></span> of <span class="totalPages"></span></div>',
  margin: { top: '22mm', bottom: '20mm', left: '15mm', right: '15mm' }
});

Reserve enough top and bottom margin for these templates. Header and footer markup does not automatically inherit the page’s application styles, so use inline styles in the templates.

Common failures and fixes

  • A print dialog opens. Your code is calling window.print() or clicking a print button that does so. Replace that flow with page.pdf(); the dialog-testing example in Playwright’s dialogs guide is for observing window.print(), not for creating a PDF.
  • page.pdf is not a function. Check that the object is a Playwright Page, not a different browser automation library, and update the package if your installed version is unusually old.
  • The browser executable is missing. Run the matching npx playwright install chromium command in the same environment as the application, including your CI image.
  • The PDF is blank or missing late content. Wait for a page-specific ready selector, fonts and images. A successful navigation only proves that the initial document loaded.
  • Backgrounds or colors are absent. Set printBackground: true and inspect the page’s print CSS. If colors still differ, evaluate -webkit-print-color-adjust for the relevant elements.
  • Content is clipped or unexpectedly scaled. Check paper format, explicit width and height, margins, scale, and whether preferCSSPageSize is allowing an @page rule to override your format.
  • Images or fonts shift between runs. Wait for the actual assets, use stable test data, and avoid printing while transitions or carousels are still moving.
  • Only some pages are needed. Set pageRanges with the documented syntax and verify the resulting page count, especially when content reflows.
  • The job hangs. Give goto and readiness waits explicit timeouts, log the URL and stage that failed, and always close the browser in finally.

JavaScript and Python bindings

The JavaScript API is shown above. Python projects use the same PDF concepts through the Playwright binding:

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto('https://example.com', wait_until='load')
    page.pdf(path='page.pdf', format='A4', print_background=True)
    browser.close()

In either language, keep the media choice, readiness condition and PDF options together in the job that owns them. That makes a failed export diagnosable and prevents a global setting from silently changing unrelated documents.

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

What this does—and does not—test

Calling page.pdf() is document generation, not a test that a user successfully opened a system print dialog. If your test requirement is specifically “clicking this control invokes window.print(),” use Playwright’s dialog event handling and assert that the call occurred. If the requirement is a downloadable PDF, skip that browser-dialog flow and generate the bytes with page.pdf().

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

Or skip the browser setup

ScreenshotNeo provides a website capture API and an MCP server for AI agents. It can capture a page as PNG, JPEG, WebP or PDF, and its capture_pdf MCP tool is useful when an agent needs document output without you maintaining Playwright browsers.

For a direct request, the documented cURL form is:

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

Equivalent Python and Node.js requests are:

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(`HTTP ${res.status}`);
const body = Buffer.from(await res.arrayBuffer());

Read the current request and PDF details in the ScreenshotNeo documentation. Before capture, it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups and chat widgets; each cleanup step can be disabled. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and the response identifies the page verdict and billing status with X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.

Every feature is included on every plan. The free plan includes 1,000 shots per month with no card; paid plans are 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 provides two months free. If you want clean captures without installing browsers, create a free ScreenshotNeo account.

Frequently Asked Questions

Can a PDF generated by Playwright be sent directly from an API endpoint?

Yes. Omit the path option, receive the returned buffer, set Content-Type: application/pdf, and send the bytes in your framework’s response.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

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

Does generating a PDF require a physical printer or operating-system printer configuration?

No. Playwright writes PDF bytes in the browser process; it does not submit a job to an installed printer.

Where should I check for option changes?

Use the Page API reference that matches the Playwright version installed in your project, because defaults and available options can change between versions.

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.