# 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": "

Invoice #042

", "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 = `

Invoice #042

Design work$120.00
`; 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: "", footerTemplate: `
Page of
`, 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": '
' 'Page of
', }, 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 `` and ``. 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.