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

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

The shortest reliable answer: use WeasyPrint when you control the HTML and want a direct HTML/CSS-to-PDF API; use Playwright when the document depends on browser navigation, JavaScript, or Chromium rendering. Both are documented Python workflows, but they have different installation and deployment requirements.

Choose the rendering approach first

HTML-to-PDF conversion is not one standardized operation. A direct renderer parses your markup and styles, while a browser loads a page and prints it. Your choice should follow the document, not a supposed universal speed or fidelity ranking: the available documentation does not establish a controlled comparison for representative workloads.

Requirement Better starting point Why
Generated reports with controlled HTML and CSS WeasyPrint A compact HTML(...).write_pdf(...) API and no browser download.
Pages that navigate, execute JavaScript, or rely on browser behavior Playwright Creates a real Chromium page and calls page.pdf().
Production deployment Either, after a document test suite WeasyPrint needs native text/layout libraries; Playwright needs browser binaries.

For either path, test representative pages for fonts, images, links, page breaks, headers and footers, and any PDF conformance requirement before committing to an engine.

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

WeasyPrint: direct HTML and CSS rendering

WeasyPrint’s documented quickstart constructs an HTML object and writes a PDF. The current documentation identifies version 70.0 and lists Python 3.10 or newer and Pango 1.44 or newer among the requirements. Platform-specific native packages still apply, so follow the installation instructions for your operating system.

Install the package and native prerequisites

  1. Install the native libraries listed for your operating system in the WeasyPrint installation guide, including a supported Pango installation.
  2. Create or activate a virtual environment running Python 3.10 or newer.
  3. Install the Python package:
    python -m pip install weasyprint

Minimal conversion from a string

from weasyprint import HTML

HTML(string="""
    <h1>Monthly report</h1>
    <p>Generated from HTML with Python.</p>
""").write_pdf("report.pdf")

Run the script from a directory where the process can create report.pdf. The same API accepts HTML from a URL, filename, or file object. Calling write_pdf() without a destination returns PDF bytes, which is useful in a web response or object-storage upload.

Write bytes instead of a local file

from weasyprint import HTML

pdf_bytes = HTML(string="<h1>Invoice 1042</h1>").write_pdf()
with open("invoice.pdf", "wb") as output:
    output.write(pdf_bytes)

Convert an HTML file and resolve relative assets

from weasyprint import HTML

HTML(filename="templates/report.html",
     base_url="templates/").write_pdf("report.pdf")

base_url gives relative images, stylesheets, and fonts a known location. Without an appropriate base URL, a file that looks correct in a browser can produce a PDF with missing assets.

Provide CSS explicitly

from weasyprint import CSS, HTML

html = HTML(string="""
<!doctype html>
<html><head><meta charset="utf-8"></head>
<body><h1>Quarterly report</h1></body></html>
""")
css = CSS(string="""
@page { size: A4; margin: 18mm; }
body { font-family: sans-serif; color: #222; }
h1 { break-after: avoid; }
""")
html.write_pdf("quarterly.pdf", stylesheets=[css])

Use print-oriented CSS such as @page, margins, and break rules. Keep asset URLs accessible to the conversion process and avoid assuming that every browser-only CSS or JavaScript feature is available in a direct renderer.

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.

Playwright: print a Chromium page to PDF

Playwright’s Python API launches a browser, creates or navigates a page, and calls page.pdf(). The API reference says PDF generation uses print CSS media by default. If your design is written for the screen, call page.emulate_media(media="screen") before generating the PDF.

Install Python and browser binaries

  1. Install the package: python -m pip install playwright.
  2. Download the browser binaries required by your deployment: playwright install.
  3. In a container or CI image, repeat the browser installation during image build and ensure the runtime user can execute the binaries. See the library setup and browser installation documentation.

Render an HTML string

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.set_content("<h1>Monthly report</h1><p>Rendered in Chromium.</p>")
    page.pdf(path="report.pdf")
    browser.close()

The browser is closed in the same context so the process does not leak resources. For a long-running service, create a controlled browser lifecycle and close each page after its job.

Navigate to a URL and wait for page work

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.pdf(path="example.pdf", format="A4", print_background=True)
    browser.close()

Use a less aggressive readiness condition when a site keeps analytics or streaming requests open forever. For application pages, wait for a specific selector that proves the report is populated rather than relying only on a timer.

Choose screen media deliberately

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.set_content("<link rel='stylesheet' href='report.css'><main>Report</main>")
    page.emulate_media(media="screen")
    page.pdf(path="screen-styled.pdf", format="A4", print_background=True)
    browser.close()

Without emulate_media, Playwright uses print media. That can intentionally hide navigation, change colors, or apply print-only break rules.

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

Fonts, images, links, and page breaks

Make assets deterministic

  • Package fonts and images with the application or serve them from an authenticated endpoint that the renderer can reach.
  • Use an explicit base URL with WeasyPrint and wait for required resources in Playwright.
  • Check the generated PDF, not just the browser preview: missing fonts can change line wrapping and push headings onto another page.

Control pagination with CSS

.invoice-line { break-inside: avoid; }
.page-break { break-before: page; }
@page { size: Letter; margin: 0.6in; }

Keep tables and signature blocks together where possible, but accept that a renderer may still move content when a block cannot fit on a page. Create test fixtures for unusually long names, large tables, and empty sections.

Security boundaries for untrusted input

Do not treat arbitrary user-supplied HTML, CSS, URLs, or scripts as safe to render. WeasyPrint’s documentation explicitly warns: “Using WeasyPrint with untrusted HTML or untrusted CSS may lead to various security problems.” The common use cases guidance should be part of your threat model.

  • Allow-list templates, CSS properties, URL schemes, and remote hosts.
  • Isolate conversion workers and apply CPU, memory, time, and output-size limits.
  • For Playwright, disable or restrict access to internal network addresses when users can control navigation.
  • Sanitize data before inserting it into HTML and never interpolate untrusted strings into executable JavaScript.

Troubleshooting common failures

WeasyPrint cannot import or start

Cause: a missing or incompatible native dependency, commonly among the text and layout libraries. Fix: install the packages listed for your OS in the current first-steps guide, verify Python and Pango versions, then reinstall the Python package inside the active virtual environment.

Images or styles are missing

Cause: relative URLs have no base location, or the worker cannot reach a remote asset. Fix: pass base_url, use stable absolute URLs, and verify file permissions and network access from the conversion process.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

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

Playwright reports that an executable is missing

Cause: the Python package is installed but browser binaries are not. Fix: run playwright install during setup or image build, and confirm the runtime user can read and execute the installed files.

The PDF looks different from the web page

Cause: Playwright prints with print media by default, or the direct renderer does not implement a browser-only feature. Fix: try page.emulate_media(media="screen") when screen styling is intended, replace unsupported dependencies, and maintain visual regression samples.

The page is blank or incomplete

Cause: capture occurred before data or fonts loaded. Fix: wait for a meaningful selector or application-ready signal, then inspect browser console and network errors. Avoid an arbitrary long sleep as the only readiness test.

Performance, reliability, and cost planning

Neither source set supplies a universal speed or fidelity winner. Measure your own templates with realistic data. WeasyPrint avoids downloading a browser, which can simplify a small worker; Playwright’s browser process adds installation and memory planning but handles browser navigation and JavaScript.

  • Reuse a controlled Playwright browser where safe, while creating a fresh page per job.
  • Set explicit timeouts and cancel stuck jobs.
  • Record renderer version, template version, input identifiers, duration, and failure reason.
  • Keep output bytes in memory only when sizes are bounded; otherwise stream or write to managed temporary storage.
  • Run representative tests after upgrading either the Python package, native libraries, or browser binaries.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server that can return PNG, JPEG, WebP, or PDF from one GET request. It accepts cookie and consent banners like a visitor, then removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.

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

For a direct call, use the documented endpoint (see the ScreenshotNeo API documentation):

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

The same request from Python:

import requests

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

And Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('shot.webp', data);

ScreenshotNeo also offers an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. Every plan includes all features; 1,000 shots per month are free with no card, Starter is $5 for 3,000, and paid plans start at that $5 level. Sign up free to try it without a card.

FAQ

Can I return PDF bytes directly from a web framework?

Yes. With WeasyPrint, omit the destination in write_pdf(), then send the returned bytes with a PDF content type and a suitable download header.

Does Playwright always need Chromium?

The documented Python workflow requires Playwright browser binaries. Install them separately with playwright install and include them in deployment planning.

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

Which engine should I use for JavaScript-generated content?

Start with Playwright because it creates a browser page and can wait for application state. Validate the exact page and security model before production.

Is user-provided HTML safe to convert?

No. Treat markup, CSS, URLs, and scripts as untrusted until sanitized and isolated; rendering itself can expose security risks.

Frequently Asked Questions

What is the simplest Python HTML-to-PDF API?

For controlled HTML, WeasyPrint’s HTML(...).write_pdf(...) is the shortest documented path.

Why is my Playwright PDF using the wrong colors or layout?

page.pdf() uses print CSS media by default; call page.emulate_media(media="screen") when you need screen styles.

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

What should I test before shipping generated PDFs?

Test long text, fonts, images, links, tables, page breaks, missing assets, and the renderer versions used in your deployment.

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.