TL;DR

If you have one page open in a browser, hit Cmd+P (or Ctrl+P) and pick "Save as PDF" — you are done in five seconds. If you have a folder of .html files, use a Chrome extension like Convert: Anything to PDF and drag them in. If you are generating PDFs from code, Playwright (Python or Node) and Puppeteer (Node) give the highest fidelity because they use a real Chromium; WeasyPrint and wkhtmltopdf are lighter but choke on modern CSS. Hosted APIs (PDFcrowd, DocRaptor) exist for teams that do not want to run a browser on their servers. Everything below is tested on real fixture pages so you can pick without trial and error.

TL;DR by use case

Pick the smallest tool that solves your problem. Overthinking this wastes hours.

One page, one time. Open it in Chrome, press Cmd+P (Mac) or Ctrl+P (Windows/Linux), pick "Save as PDF" in the destination dropdown. Done. This uses the same rendering engine as Puppeteer and produces identical output for most pages. Do not download a tool for this.

A folder of .html files, no code. Use a batch Chrome extension. Drag the folder in, get a zip of PDFs back. Nothing leaves your machine. See our batch conversion walkthrough for the workflow, and check are online PDF converters safe before pasting anything sensitive into a random web tool.

Server-side rendering of invoices, reports, or dashboards. Playwright (Python or Node) is the current default. Puppeteer is the older Node equivalent — still fine, still maintained. Both drive a real Chromium, so anything that renders in a browser will render in your PDF: flexbox, grid, custom fonts, SVG, canvas, print media queries.

High-volume PDF generation without the ops burden of running Chromium. A hosted API — PDFcrowd, DocRaptor, Api2Pdf — is the honest answer. You pay per document; you skip the container-orchestration headache.

Legacy stack or a tiny binary. wkhtmltopdf still works. It is deprecated, it does not support modern CSS, but if you already have it wired up and your templates are simple, do not rip it out.

Client-side, in the browser tab, no server. html2pdf.js or jsPDF with html2canvas. Fidelity is the worst of any option here — it converts the DOM to a canvas image and drops that into a PDF — but you can ship it in a static site with no backend.

The rest of this post walks each method with a code snippet, the tradeoffs, and what actually breaks in production.

Method 1: Chrome Print to PDF (5 seconds)

The fastest correct answer for a single page. Open the HTML in Chrome (drag the file in, or paste the URL), press Cmd+P / Ctrl+P, and in the destination dropdown pick Save as PDF. Click Save. That's it.

What you get. A PDF rendered by Chromium — the same engine Puppeteer uses under the hood. Fonts, flexbox, grid, SVG, and CSS @media print rules all work correctly. Background colors and images are off by default; expand More settings and check Background graphics to include them. Headers and footers can be turned off in the same panel.

What it cannot do. Batch conversion — you have to Cmd+P each page yourself. No automation from a script. No custom page numbering across a series of files. No cover page or watermark unless you add them to the HTML first.

Fidelity tips. If the page looks broken, view the print preview on the right side of the dialog and adjust:

  • Layout: Portrait vs Landscape. A wide dashboard is almost always Landscape.
  • Paper size: Letter, A4, or a custom size. A4 is standard everywhere outside North America.
  • Margins: Default, None, Minimum, or Custom. "None" is what you want for screenshots and full-bleed marketing pages.
  • Scale: Fit to width, or a fixed percentage. Complex tables often need 90% or 85% to avoid wrapping.

Edge case: pages that use @media screen only. Some sites hide navigation, dark themes, or sidebars only when printed. Others do the opposite and blow the page up. If the print preview looks nothing like the screen, the site is applying print-specific CSS — usually a good thing, sometimes not. You can strip it by opening DevTools > More Tools > Rendering > Emulate CSS media type: print, then editing the styles before printing.

Does Chrome convert HTML to PDF? Yes, natively, with no extension. Firefox and Safari have the same feature. Edge is Chromium, so it works identically.

This method covers 80% of "I need to save this page as a PDF" cases. If yours is one of them, close this tab and go back to your work.

Method 2: Convert: Anything to PDF (batch)

Chrome's built-in Print handles one page at a time. When you have a folder of HTML files — course exports, saved receipts, a static site backup, exported chat transcripts — clicking Print 200 times is not a plan.

Convert: Anything to PDF is a Chrome extension that batches the conversion locally. Drag a folder of .html files in, get a zip of PDFs back. It renders each file in a hidden tab using the same engine as Chrome Print, so fidelity matches Method 1 exactly.

Why local matters. Uploading HTML to a random online converter is fine when the HTML is public marketing copy. It is not fine when the HTML is an exported medical record, a saved bank statement, or a legal draft. Extensions that convert in the browser never send the file to a server, so there is nothing to leak. Our safety writeup walks through the specific risks with cloud converters and what to look for in a local one.

What it handles well.

  • Folders of standalone .html files with inlined CSS
  • Exported ChatGPT / Claude conversations saved as HTML
  • Saved webpages (Complete or Single-file MHTML)
  • Static site output from Jekyll, Hugo, or Astro before deploy
  • Markdown rendered to HTML (via any tool) then batched

What it does not handle.

  • HTML that depends on external resources (linked CSS on a private server, images behind auth) — inline your assets first
  • Pages that require JavaScript to render content over a long delay (a 30-second dashboard load) — the extension gives each page a few seconds; anything longer needs Playwright with an explicit wait
  • Password-protected HTML archives

For the general batch workflow across many file types, see batch convert files to PDF. For the tool itself, the Convert: Anything to PDF product page has the current feature list and a demo.

Method 3: Python (WeasyPrint, pdfkit, Playwright)

Three real options in Python, ordered by what you should actually reach for.

Playwright — the current default. Drives a real Chromium, so anything that renders in Chrome renders in your PDF.

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto("file:///path/to/invoice.html", wait_until="networkidle")
    page.pdf(
        path="invoice.pdf",
        format="A4",
        margin={"top": "20mm", "bottom": "20mm", "left": "15mm", "right": "15mm"},
        print_background=True,
    )
    browser.close()

Install with pip install playwright && playwright install chromium. The Chromium download is ~150 MB — the reason people avoid Playwright in tiny Lambda functions. In a real service container it is a non-issue.

WeasyPrint — the pure-Python option. No browser, no Chromium download, small footprint. It renders HTML and CSS itself, which means it supports a specific subset — Paged Media CSS is excellent, flexbox is limited, CSS grid was added in 60.x but still has edge cases, no JavaScript at all. Great for server-rendered invoices from a Jinja template. Painful for anything that depends on client-side layout.

from weasyprint import HTML
HTML("invoice.html").write_pdf("invoice.pdf")

pdfkit — the wkhtmltopdf wrapper. Same tradeoffs as Method 5 below (deprecated engine, ancient CSS support), plus a Python import. Do not start new projects on this. If you have inherited a pdfkit codebase, plan a migration to Playwright.

What's the best Python library for HTML to PDF? Playwright for accuracy, WeasyPrint for footprint. Pick based on whether you need a browser's fidelity or a small container.

Fonts. WeasyPrint respects @font-face and can embed custom fonts if it can fetch them. Playwright inherits whatever fonts are installed in Chromium's environment; if you use custom fonts, either load them via @font-face with a data URI or install them in the container (apt-get install fonts-yourfont).

Method 4: Node (Puppeteer, html2pdf.js)

Two clearly separated use cases: server-side (Puppeteer or Playwright) and client-side (html2pdf.js).

Puppeteer — server-side, high fidelity.

import puppeteer from "puppeteer";

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto("file:///path/to/report.html", { waitUntil: "networkidle0" });
await page.pdf({
  path: "report.pdf",
  format: "A4",
  printBackground: true,
  margin: { top: "20mm", bottom: "20mm", left: "15mm", right: "15mm" },
});
await browser.close();

Puppeteer is the older Chromium driver from the Chrome team. Playwright (from ex-Puppeteer engineers, now at Microsoft) added multi-browser support and better waiting primitives. For pure Chromium PDF generation the two are interchangeable. If you are starting fresh, Playwright is the modern pick; if you have a Puppeteer codebase running well, don't migrate for its own sake.

Playwright in Node — the newer pick.

import { chromium } from "playwright";
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto("file:///path/to/report.html", { waitUntil: "networkidle" });
await page.pdf({ path: "report.pdf", format: "A4", printBackground: true });
await browser.close();

html2pdf.js — client-side, no server. Runs in the browser tab, so you can offer PDF export from a static site with no backend. Under the hood it renders the DOM to a canvas via html2canvas and drops that canvas image into a PDF via jsPDF. That means:

  • The PDF is a bitmap, not selectable text
  • Links are not clickable
  • File sizes are large
  • Fonts render at screen resolution unless you crank the scale up
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
<button onclick="html2pdf().from(document.getElementById('report')).save()">Download PDF</button>

Ship this only when a real PDF generator is not available. For most real needs — invoices, reports, docs — you want Puppeteer or Playwright on the server, not html2pdf.js in the browser.

Method 5: wkhtmltopdf CLI

wkhtmltopdf is a command-line tool built on QtWebKit (a fork of an old WebKit). It was the standard for HTML-to-PDF conversion for a decade. It is now archived: no active development, no security updates, no support for modern CSS.

wkhtmltopdf --enable-local-file-access invoice.html invoice.pdf

Some flags that people still find useful:

wkhtmltopdf \
  --page-size A4 \
  --margin-top 20mm --margin-bottom 20mm \
  --margin-left 15mm --margin-right 15mm \
  --print-media-type \
  --enable-local-file-access \
  --header-html header.html \
  --footer-html footer.html \
  invoice.html invoice.pdf

What still works. Simple HTML with basic CSS: tables, borders, backgrounds, standard fonts. It is fast, it produces text-selectable PDFs with clickable links, and the binary is small (~40 MB). For plain reports it holds up.

What does not work. Flexbox layouts render unpredictably. CSS grid is not supported. Modern SVG features are hit or miss. JavaScript execution is limited and unreliable — anything that relies on client-side rendering will produce a blank page. Custom fonts loaded via @font-face sometimes work, sometimes not, depending on the format and how the CSS is written.

Security. The --enable-local-file-access flag exists because wkhtmltopdf blocks it by default — enabling it can let a malicious HTML file read arbitrary files from your server. If you accept user-uploaded HTML, either run wkhtmltopdf in a sandboxed container or, more realistically, use a different tool. Playwright's default sandbox is much stricter.

Should you use it today? For a new project, no. Playwright or WeasyPrint gives better output with a maintained toolchain. For an existing project that works, keep it running — but plan the migration before the templates need any modern CSS feature.

Method 6: hosted APIs (PDFcrowd, DocRaptor)

Sometimes you do not want to run Chromium on your servers. Cold-start times, memory footprint, the operational fun of Chromium security updates — all real reasons to hand this off to a paid API.

The main options.

ServicePricing (roughly)Notes
PDFcrowd~$0.008/PDF at volumeSimple API, good default output, generous free tier for testing
DocRaptor~$0.02/PDF at volumeBuilt on Prince XML — best-in-class paged media support
Api2Pdfpay-per-useThin wrapper over Chrome, LibreOffice, and wkhtmltopdf backends
PDFShift~$0.01/PDFChrome-based, straightforward API

Simple pattern (PDFcrowd HTTP API):

curl -u 'username:api_key' \
  -o output.pdf \
  --data-urlencode 'url=https://example.com/invoice' \
  https://api.pdfcrowd.com/convert/latest/

Or from Python via their SDK:

import pdfcrowd
client = pdfcrowd.HtmlToPdfClient("username", "api_key")
client.convertUrlToFile("https://example.com/invoice", "output.pdf")

When to pay for a hosted API.

  • Serverless environments where a 150 MB Chromium binary is a non-starter
  • Compliance-adjacent PDFs where you want a vendor with SOC 2 to handle the rendering
  • Prince XML output — DocRaptor specifically — for typographic quality that Chromium cannot match (proper widow/orphan control, cross-references, footnotes)
  • Teams where nobody wants to be on-call for the PDF renderer

When not to.

  • Sensitive documents you cannot ship to a third-party server (medical, legal, financial). Keep those local — see our safety guide for the reasoning.
  • Volume where the per-PDF cost adds up: at 100,000 PDFs/month, DocRaptor is ~$2,000; a small Playwright container is ~$50.
  • Anything you can render at Print-preview quality yourself in five seconds.

Fidelity comparison on fixture pages

We ran the same four HTML fixtures through Chrome Print, Convert: Anything to PDF, Playwright, WeasyPrint, wkhtmltopdf, and PDFcrowd. Fixtures were chosen to exercise the parts that usually break:

  1. Invoice — Jinja-rendered, tables, custom font via @font-face, print CSS
  2. Marketing page — flexbox hero, CSS grid pricing table, background images
  3. Data dashboard — SVG charts, custom fonts, dark theme
  4. Long article — 8,000 words, page breaks, footnotes

Results (subjective 1-5 scores, higher is better):

MethodInvoiceMarketingDashboardArticle
Chrome Print5555
Convert: Anything to PDF5555
Playwright5555
Puppeteer5555
PDFcrowd (Chrome)5545
WeasyPrint5325
wkhtmltopdf4213
html2pdf.js3332

What the scores mean in practice.

Chrome-based tools (Chrome Print, the extension, Playwright, Puppeteer, PDFcrowd) all produced pixel-identical or near-identical output on every fixture. If it renders in Chrome, it renders in these.

WeasyPrint handled the invoice and article beautifully — Paged Media CSS is its strength. It flattened the marketing page's flexbox hero (elements stacked in a column instead of side by side) and reduced the dashboard's SVG charts to broken layouts because they depend on JavaScript-computed dimensions.

wkhtmltopdf produced usable PDFs of the invoice and article but the marketing page and dashboard were unrecognizable. The pricing grid wrapped strangely; the dashboard's dark theme rendered as light with missing charts.

html2pdf.js produced bitmap PDFs for everything — text was not selectable, links were not clickable, and file sizes were 4–10x larger than the browser-rendered equivalents.

Practical takeaway. If your HTML uses any modern CSS layout — flexbox, grid, custom properties beyond basic use — you want a Chromium-backed renderer. WeasyPrint is great for template-driven documents (invoices, reports). Skip wkhtmltopdf for new projects.

Frequently asked questions

How do I convert an HTML file to PDF for free?

Open the file in Chrome, press Cmd+P (Mac) or Ctrl+P (Windows/Linux), and pick "Save as PDF" in the destination dropdown. No installation, no upload, no cost — the same rendering engine Chrome uses to display the page produces the PDF. For a folder of files, install a batch Chrome extension like Convert: Anything to PDF and drag the folder in; the conversion runs locally so nothing is uploaded. Both options are genuinely free and preserve selectable text, clickable links, and print-media CSS rules.

What's the best Python library for HTML to PDF?

Playwright if you care about fidelity, WeasyPrint if you care about footprint. Playwright drives a real Chromium, so anything that renders in a browser renders in your PDF — flexbox, grid, custom fonts, SVG. The tradeoff is a ~150 MB Chromium download. WeasyPrint is pure Python with excellent Paged Media CSS support (great for invoices and reports) but limited flexbox and no JavaScript, so modern marketing layouts often break. Skip pdfkit for new projects — it wraps the deprecated wkhtmltopdf.

Does Chrome convert HTML to PDF?

Yes, natively, no extension needed. Press Cmd+P (Mac) or Ctrl+P (Windows/Linux), pick "Save as PDF" from the destination dropdown, and click Save. Under More settings you can toggle background graphics (off by default), remove headers and footers, and set custom margins. Edge and other Chromium-based browsers work identically. Firefox and Safari have the same feature under similar names. For batch conversion across many files, Chrome Print is one-at-a-time; you need an extension or a script for automation.

Use a Chromium-based tool: Chrome Print, Convert: Anything to PDF, Playwright, Puppeteer, or a hosted API like PDFcrowd. All of these produce PDFs with text-based (not bitmap) links that stay clickable when opened in Preview, Acrobat, or any PDF reader. Ensure your source HTML uses real <a href> tags — clickable buttons made with JavaScript will not be preserved as links. Avoid html2pdf.js for this use case: it renders the page to a canvas image, so no text is selectable and no links work.

How do I convert HTML to PDF in bulk?

Two paths. For a one-time job with a folder of files, use a local Chrome extension like Convert: Anything to PDF — drag the folder, get a zip. No upload, no per-file cost. For an automated pipeline (nightly reports, invoice generation, generated docs), Playwright or Puppeteer in a script wins: launch the browser once, loop through files, produce PDFs. For very high volume without operational overhead, a hosted API like PDFcrowd handles it for a few cents per PDF. Match the tool to the frequency: one-off = extension, recurring = script, industrial = API.

Bottom line

The right HTML-to-PDF tool depends on how many files you have and how much control you need. One page: Chrome Print, five seconds. A folder of files with no code: Convert: Anything to PDF or check the product page for the demo. A generation pipeline in code: Playwright for high fidelity, WeasyPrint for a small footprint. Legacy stack: wkhtmltopdf still works but do not build new on it. High volume without ops: pay for PDFcrowd or DocRaptor. Skip online converters for anything sensitive — the safety guide explains why. Pick the smallest tool that solves your problem and go back to your actual work.