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

For static, document-shaped HTML and CSS, use WeasyPrint: install its Python package and required native libraries, create an HTML object, then call write_pdf(). If the page needs JavaScript or browser APIs, use Puppeteer to print it with Chromium instead. In either case, set up asset URLs and print CSS deliberately, verify the rendered pages, and isolate untrusted input.

Choose a renderer that matches the page

HTML-to-PDF conversion is not one uniform operation. A report or invoice is usually a paged document: its layout should flow across pages, repeat headers where needed, and handle margins and page breaks. A JavaScript application, by contrast, may need to run in a browser before its content exists. The first decision is whether you need a document renderer or a browser.

Option Best fit Important trade-off
WeasyPrint Python projects producing documents from HTML and CSS Uses a paged-media rendering model, not a full browser; check its support for the CSS features your document uses.
Puppeteer Pages that need JavaScript, browser APIs, or Chromium-compatible rendering Requires a compatible Chromium installation and explicit decisions about print versus screen styles.
wkhtmltopdf Legacy applications that depend on its existing output Its stable 0.12.6 series was released June 11, 2020; treat it as a compatibility choice rather than the default for a new system.

For a Python-first service that produces invoices, certificates, or reports from templates, start with WeasyPrint. Choose Puppeteer if the page is effectively a web app that must render in a browser before printing. There is no single renderer that guarantees identical output for every CSS feature: test the actual templates, fonts, images, and page breaks you rely on.

Convert HTML to PDF with WeasyPrint in Python

WeasyPrint is suited to HTML and CSS designed as paged documents. It exposes a Python API as well as a command-line program. Installation may require native Pango-related libraries in addition to the Python package; the exact system packages vary by operating system and deployment image.

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

Install and check the environment

  1. Install Python and the Pango-related native dependencies required for your operating system.
  2. Install the Python package: pip install weasyprint.
  3. Check the detected environment with weasyprint --info. Resolve missing-library errors before wiring conversion into an application.

The command below assumes the native prerequisites are already installed. Keep the input HTML and its assets together, or use absolute URLs for remote assets.

Runnable Python example

from pathlib import Path
from weasyprint import HTML

source = Path("invoice.html")
output = Path("invoice.pdf")

HTML(filename=str(source), base_url=str(source.resolve().parent)).write_pdf(output)
print(f"Wrote {output.resolve()}")

Setting base_url gives relative references such as images/logo.png a location to resolve against. Without a deliberate base URL, local images, stylesheets, or fonts may be missing even when the HTML itself converts. You can also use absolute asset URLs, provided the conversion environment is permitted to reach them.

Use print CSS to control pages

Specify paper size, margins, and page-specific layout rather than expecting screen dimensions to map neatly to paper. For example:

@page {
  size: A4;
  margin: 18mm;
}

@media print {
  .screen-only { display: none; }
  h1, h2 { break-after: avoid; }
  table, figure { break-inside: avoid; }
}

Adapt page size and margins to the document and the region where it will be printed. Break-avoidance rules are preferences, not a guarantee that a large element can fit on one page. For long tables, test how rows split and whether the header remains useful. Check the CSS support of your chosen WeasyPrint release for advanced layout properties rather than assuming every browser feature is implemented.

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

Use the command line for a quick conversion

For a file-based job, the installed executable can convert a source file directly:

weasyprint invoice.html invoice.pdf

For a Python application, the API is usually easier to integrate with template rendering, logging, and application-level validation. Either path still needs access to the fonts and other resources referenced by the document.

Use Puppeteer when the page needs a browser

WeasyPrint is not a substitute for running a JavaScript application. If the content appears only after scripts execute, or the layout depends on browser APIs and Chromium behavior, Puppeteer can load the page in a browser and create a PDF with Page.pdf(). Puppeteer uses the print CSS media type by default. If you need the screen stylesheet, explicitly select it with page.emulateMediaType('screen').

Install Puppeteer

In a new Node.js project, install Puppeteer:

npm install puppeteer

Use a compatible Chromium installation in the runtime environment. Whether Chromium is installed or downloaded as part of your setup depends on how Puppeteer is installed and deployed; check that the browser can launch in the same container or host that runs the conversion.

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.

Runnable browser-to-PDF example

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ headless: true });
  try {
    const page = await browser.newPage();
    await page.goto('https://example.com/report', {
      waitUntil: 'networkidle0',
      timeout: 60000,
    });

    // Use this when the PDF should match screen CSS instead of print CSS.
    // await page.emulateMediaType('screen');

    await page.evaluate(() => document.fonts.ready);
    await page.pdf({
      path: 'report.pdf',
      format: 'A4',
      printBackground: true,
      margin: { top: '18mm', right: '18mm', bottom: '18mm', left: '18mm' },
    });
  } finally {
    await browser.close();
  }
})();

Replace the example URL with the page you control. Wait for the application’s data and assets, not just initial navigation: some pages continue loading content after the network becomes quiet. If the page has a reliable completion signal, wait for that selector or application state before printing. Waiting for document.fonts.ready helps avoid capturing before web fonts finish loading; still inspect the PDF for missing or substituted fonts.

Make the output reliable and safe

Check assets, pagination, and PDF behavior

  • Relative resources: give WeasyPrint an intentional base URL, or use absolute asset URLs. Ensure the service can reach remote assets if it needs them.
  • Fonts: make fonts available to the renderer and wait for browser font loading when using Puppeteer. Verify the resulting PDF rather than assuming a successful conversion means every font embedded or displayed as intended.
  • Page breaks: inspect headings at page bottoms, table splits, oversized images, and blank pages. Adjust the source content and print CSS, then re-render.
  • Visual styling: Puppeteer prints using print media by default. Select screen media only when that is the intended appearance; WeasyPrint also requires CSS features to be supported by its renderer.
  • Document requirements: check whether the chosen renderer meets your needs for hyperlinks, bookmarks, attachments, forms, PDF/A, or PDF/UA. WeasyPrint documents support for these capabilities, but verify the specific output requirement and workflow you need.

Do not treat user HTML as harmless

HTML and CSS can reference files and network resources, and scripts may be active in browser-based workflows. WeasyPrint warns about security problems with untrusted sources. The wkhtmltopdf project warns that unsafe HTML or JavaScript can lead to server takeover. Treat submitted markup, stylesheets, and URLs as hostile input.

  • Sanitize or reject user-supplied HTML and CSS according to your application’s needs.
  • Run conversion in an isolated process or container with limited filesystem permissions and no unnecessary secrets.
  • Restrict outbound network access and local-file access; do not let arbitrary input choose resources the service can reach.
  • For Puppeteer, avoid executing untrusted pages in a privileged environment, and limit browser permissions and runtime resources.
  • Apply timeouts and resource limits so a slow, oversized, or resource-intensive page cannot monopolize a worker.

Do not use wkhtmltopdf for untrusted HTML unless you have addressed the project’s security warning and have appropriate isolation. Its older WebKit behavior may still matter when preserving a legacy output, but that is a reason to test a migration carefully, not to assume current browser compatibility.

Or skip the browser setup

If the input is a public webpage and you need a rendered capture rather than a locally controlled HTML-to-PDF pipeline, ScreenshotNeo offers a screenshot API and MCP server. It can return screenshots or PDFs; its PDF options include paper size, margins, landscape, and page ranges. The example below is specifically a screenshot request saved as WebP, not a PDF request. Use the documentation for the PDF request configuration.

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

cURL example:

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

Python example:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js example:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the ScreenshotNeo API documentation for request options. Before a capture, it accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each of those steps can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and responses identify the page verdict and billing status in headers. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf for AI agents and MCP clients.

The free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000. These are plan allowances, not a replacement for testing whether a remote page is suitable for a formal document workflow. Sign up for ScreenshotNeo’s free plan.

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

Troubleshoot common conversion failures

Symptom Likely cause What to check
WeasyPrint fails to import or weasyprint --info reports missing libraries Native dependencies are missing or unavailable in the runtime environment. Install the Pango-related dependencies for that operating system, then rerun the environment check in the same container or host as the app.
Images, CSS, or fonts are absent Relative paths have no usable base URL, or assets cannot be reached. Set base_url, use valid absolute URLs, and verify filesystem and network access from the conversion process.
PDF content is blank or incomplete The page may depend on JavaScript or data that has not loaded. Use Puppeteer for browser-dependent pages and wait for the application’s data-ready signal before printing.
Browser PDF looks different from the visible page Page.pdf() uses print media by default, and print CSS can change layout. Decide whether print or screen styles are intended; if screen styling is required, call page.emulateMediaType('screen') before generating the PDF.
Text uses unexpected fonts Fonts may not be installed, accessible, or loaded at capture time. Make fonts available to the renderer, wait for browser font readiness, and inspect the generated PDF.
Tables or sections split awkwardly Page-break rules, available page space, and renderer support affect pagination. Revise print CSS and content dimensions, then test representative long and short documents.
Conversion hangs or consumes excessive resources A page may be waiting indefinitely on resources, or input may be too costly to render. Set navigation and job timeouts, constrain resource access, and apply process-level resource limits.

Validate before shipping

A successful call only establishes that a PDF file was produced; it does not establish that the document is readable or compliant. Add representative documents to your release checks, including long tables, page-boundary headings, missing or slow assets, and the fonts used in production. Inspect page count and visual pagination, confirm links and bookmarks when required, and test attachments or forms if your workflow depends on them. For accessibility or archival targets such as PDF/UA or PDF/A, validate against the requirement with an appropriate checker rather than relying on the file extension.

There is no performance figure that applies to all these renderers and inputs. Runtime depends on page complexity, assets, fonts, JavaScript execution, and deployment resources; benchmark your own templates under realistic concurrency. For predictable service behavior, cache only where the source and freshness rules allow it, cap concurrent jobs, and record failures with enough detail to distinguish renderer errors from unavailable assets.

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

Frequently asked questions

Can I convert an HTML string without saving it to a file?

WeasyPrint’s Python API accepts HTML content as well as a filename. When the content references relative assets, supply a base URL so those references resolve from the intended location.

Does choosing an open-source renderer guarantee an open-source output PDF?

No. The renderer’s licensing and the rights to the HTML, fonts, images, and other assets are separate considerations. Check the relevant licenses for your application and its inputs.

Is there a universal best library for HTML-to-PDF?

No. Use a paged-media renderer for document-oriented HTML/CSS and a browser engine for pages that require browser execution. Validate the CSS and PDF features that matter to your own output.

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.

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