Playwright HTML to PDF (Node.js and Python)

Playwright HTML to PDF (Node.js and Python)

Playwright HTML to PDF with page.pdf() in Node.js and Python. Covers options, headers, footers, waiting for content, and a no-browser option.

Quick answer. In Playwright, load the HTML with page.setContent(html) (or page.goto(url)) in Chromium, then call page.pdf({ path: "out.pdf", format: "A4", printBackground: true }). If you'd rather not run a browser, one API call does the same job.

curl -X POST https://html-pdf.com/v1/pdf \
  -H "Authorization: Bearer $HTML_PDF_KEY" \
  -H "Content-Type: application/json" \
  -d '{"html": "<h1>Invoice #042</h1>", "format": "A4"}' \
  -o invoice.pdf

Playwright isn't only for testing. Its page.pdf() method prints a page with Chromium, the same engine as Chrome's "Save as PDF", so it's a solid way to generate PDFs from HTML. This guide covers Node.js and Python, the options that matter, and the production issues to plan for.

Install

This step gets Playwright and its Chromium browser onto your machine.

# Node.js
npm install playwright
npx playwright install chromium

# Python
pip install playwright
playwright install chromium

On a fresh Linux server or container, add --with-deps to the install command. That also installs the system libraries Chromium needs.

PDF generation only works in Chromium. Calling page.pdf() with Firefox or WebKit throws an error.

HTML string to PDF in Node.js

This example turns an HTML string into an A4 PDF with Node.js.

// pdf.mjs (run with: node pdf.mjs)
import { chromium } from "playwright";

const html = `
  <html><head><style>
    body { font-family: system-ui, sans-serif }
    .row { display: flex; justify-content: space-between }
  </style></head>
  <body>
    <h1>Invoice #042</h1>
    <div class="row"><span>Design work</span><span>$120.00</span></div>
  </body></html>`;

const browser = await chromium.launch();
try {
  const page = await browser.newPage();
  await page.setContent(html, { waitUntil: "networkidle" });
  await page.pdf({
    path: "invoice.pdf",
    format: "A4",
    printBackground: true,
    margin: { top: "15mm", right: "15mm", bottom: "15mm", left: "15mm" },
  });
} finally {
  await browser.close();
}

HTML or URL to PDF in Python

This example saves a web page as a PDF with Python.

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="networkidle")
    page.emulate_media(media="screen")  # use screen styles instead of @media print
    page.pdf(
        path="page.pdf",
        format="A4",
        print_background=True,
        margin={"top": "15mm", "bottom": "15mm", "left": "15mm", "right": "15mm"},
    )
    browser.close()

For an HTML string, use page.set_content(html, wait_until="networkidle") instead of goto. In async code (FastAPI, for example), use playwright.async_api with await.

Options worth knowing

These are the page.pdf() options you'll use most, with their names in each language.

Node.js Python Purpose
format / width, height same Paper size, e.g. "A4", "Letter", or "210mm"
landscape same Orientation
margin same { top, right, bottom, left }
printBackground print_background Keep background colors and images
preferCSSPageSize prefer_css_page_size Let @page { size } decide
pageRanges page_ranges e.g. "1-3"
scale same 0.1 to 2
displayHeaderFooter, headerTemplate, footerTemplate display_header_footer, header_template, footer_template Repeated header and footer

This snippet adds "Page X of Y" to the bottom of every page.

await page.pdf({
  path: "report.pdf",
  format: "A4",
  displayHeaderFooter: true,
  headerTemplate: "<span></span>",
  footerTemplate: `<div style="font-size:9px;width:100%;text-align:center">
    Page <span class="pageNumber"></span> of <span class="totalPages"></span></div>`,
  margin: { top: "20mm", bottom: "20mm" },
});

Style the templates inline, because they don't see your page's CSS. Also leave a margin, or they have nowhere to render. More in headers, footers and page numbers.

Wait for the content to be ready

This section stops you from printing a page before charts or fonts have loaded.

networkidle waits for network requests to settle, but not for JavaScript that renders afterwards. Wait for a real signal instead.

await page.waitForFunction(() => window.chartsReady === true);
await page.evaluate(() => document.fonts.ready);

Production issues to plan for

Running Playwright for PDFs in production brings a few problems worth planning for.

Or skip the browser: one API call

This section shows how to get the same PDF without installing or scaling browsers. html-pdf.com runs headless Chromium for you, so the output matches page.pdf().

import os, requests

r = requests.post(
    "https://html-pdf.com/v1/pdf",
    headers={"Authorization": f"Bearer {os.environ['HTML_PDF_KEY']}"},
    json={
        "url": "https://example.com",
        "format": "A4",
        "margin": "15mm",
        "media": "screen",
        "wait_for_expression": "window.chartsReady === true",
        "footer_html": '<div style="font-size:9px;width:100%;text-align:center">'
                       'Page <span class="pageNumber"></span> of <span class="totalPages"></span></div>',
    },
    timeout=60,
)
r.raise_for_status()
open("page.pdf", "wb").write(r.content)
Playwright html-pdf.com
setContent(html) / goto(url) html / url (public pages)
printBackground print_background (on by default)
headerTemplate / footerTemplate header_html / footer_html
emulateMedia({ media: "screen" }) media: "screen"
waitForFunction(...) wait_for_expression
preferCSSPageSize, pageRanges, scale prefer_css_page_size, page_ranges, scale

There are a few extras. max_pages fails a runaway document without charge, and response: "url" returns a 24-hour download link instead of bytes. Your HTML isn't stored.

Keep Playwright if you need to log in, click through a page, or already run browsers for testing. Use the API when PDFs are the only reason you run Chromium.

curl -X POST https://html-pdf.com/v1/signup -H "Content-Type: application/json" -d '{"email": "you@example.com"}'

Then open the link we email you to confirm your address. That adds 100 free credits, and 1 credit = 1 page.

Related: Puppeteer HTML to PDF · HTML to PDF in Python · HTML to PDF in Node.js · API docs

FAQ

How do I convert HTML to PDF with Playwright?

Launch Chromium, load your HTML with page.setContent(html) or a URL with page.goto(url), then call page.pdf() with a path and format. Set printBackground: true to keep background colors.

Why does page.pdf() not work in Firefox or WebKit?

Playwright only supports PDF generation in Chromium. Calling page.pdf() with Firefox or WebKit throws an error.

How do I add page numbers to a Playwright PDF?

Set displayHeaderFooter: true and use a footerTemplate with <span class="pageNumber"></span> and <span class="totalPages"></span>. Style it inline and leave a bottom margin so it has room to render.

Can I generate PDFs without installing Playwright?

Yes. html-pdf.com runs headless Chromium for you, so you send HTML or a URL to POST https://html-pdf.com/v1/pdf and get the PDF back. You get 100 free credits, with no card required.

This page as markdown · text