# 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.

```bash
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.

```bash
# 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.

```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.

```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 |

## Page numbers in the footer

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

```js
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](https://html-pdf.com/pdf-headers-footers-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.

```js
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.

- **Browser binaries everywhere.** Each server, container and CI runner needs a matching Chromium. The official `mcr.microsoft.com/playwright` Docker images help, but they are large.
- **Memory.** Reuse one browser and open a new context or page per job. Close pages in `finally` blocks, or long-running workers leak.
- **Concurrency.** A burst of PDF requests means a burst of browser pages. You'll want a queue or a pool.
- **Serverless.** Bundling Chromium into functions runs into size limits and cold starts.
- **Untrusted input.** Rendering user HTML or URLs from inside your network can reach internal services unless you block private addresses.

## 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()`.

```python
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.

```bash
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](https://html-pdf.com/puppeteer-html-to-pdf) · [HTML to PDF in Python](https://html-pdf.com/html-to-pdf-python) · [HTML to PDF in Node.js](https://html-pdf.com/html-to-pdf-nodejs) · [API docs](https://html-pdf.com/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.
