ScreenshotNeo

BlogHTML to image & PDF

WeasyPrint HTML to PDF: Complete Python and CLI Guide

Convert HTML and CSS to reliable PDFs with WeasyPrint. Learn installation, CLI and Python workflows, assets, fonts, pagination, security, troubleshooting, and alternatives.

By the ScreenshotNeo team1 October 20268 min read

WeasyPrint converts HTML and CSS into paginated PDF files from Python or the command line. It is a Python-based visual rendering engine designed for print layout, rather than a full browser engine such as WebKit or Gecko. The current official documentation covers WeasyPrint 70.0.

For a basic conversion, install WeasyPrint and run:

weasyprint input.html output.pdf

Or use Python:

from weasyprint import HTML

HTML('input.html').write_pdf('output.pdf')

What WeasyPrint does

WeasyPrint lays out HTML and CSS for paged media and writes PDF. It supports many CSS 2.1 features and print-oriented features, plus hyperlinks, bookmarks, attachments and forms. SVG content is rendered as vectors. PDF/A and PDF/UA output can be generated, but generation does not guarantee that the resulting file conforms to those standards.

It does not execute a page like a normal interactive browser. JavaScript-driven content, hover states and focus states should not be relied on. Check the documented feature support before migrating a browser-rendered page. See the official API reference.

Install WeasyPrint

1. Create an isolated Python environment

python3 -m venv venv
. venv/bin/activate
python -m pip install --upgrade pip
python -m pip install weasyprint
weasyprint --info

WeasyPrint 70.0 requires Python 3.10 or newer. The package also depends on native libraries including Pango and pydyf. On Linux distributions, install the operating-system packages listed by the project before retrying pip if import or shared-library errors appear. Start diagnosis with your Python and Pango versions instead of assuming pip alone is sufficient. The project installation documentation lists platform-specific requirements.

2. Verify the installation

python -c "from weasyprint import HTML; print('WeasyPrint import OK')"
weasyprint --info

Command-line conversion

The command-line form is:

weasyprint [options] <input> <output>

Input and output can be filenames, URLs, or - for standard input or output.

Convert a local file

weasyprint invoice.html invoice.pdf

Convert a URL

weasyprint https://example.com/report.html report.pdf

Read HTML from standard input

cat invoice.html | weasyprint - invoice.pdf

Apply an additional stylesheet

weasyprint --stylesheet print.css invoice.html invoice.pdf

Set a base URL for relative assets

weasyprint --base-url /srv/app/static invoice.html invoice.pdf

A correct base URL is essential for relative images, stylesheets and fonts. You can also set a base with an HTML <base> element.

Useful CLI options

Option Purpose
--stylesheet FILE Add a user stylesheet.
--media-type TYPE Select the CSS media type; print is the default.
--base-url URL Resolve relative resources.
--timeout SECONDS Limit network fetch time.
--allowed-protocols Restrict URL schemes that can be fetched.
--no-http-redirects Disable HTTP redirects.
--fail-on-http-errors Fail when HTTP resources return errors.

These switches are documented in the WeasyPrint command-line reference.

Python API: complete examples

Convert a file to a PDF file

from weasyprint import HTML

HTML(filename='invoice.html').write_pdf('invoice.pdf')

Render a URL

from weasyprint import HTML

HTML(url='https://example.com/report.html').write_pdf('report.pdf')

Generate PDF bytes

from weasyprint import HTML

pdf_bytes = HTML(string='<h1>Hello</h1>').write_pdf()
with open('hello.pdf', 'wb') as output:
    output.write(pdf_bytes)

Render HTML with CSS and a base URL

from weasyprint import HTML, CSS

html = '''
<!doctype html>
<html>
  <head><title>Report</title></head>
  <body><h1>Quarterly report</h1><img src="images/chart.svg"></body>
</html>
'''

HTML(string=html, base_url='/srv/report').write_pdf(
    'report.pdf',
    stylesheets=[CSS(filename='/srv/report/print.css')]
)

Use custom fonts correctly

from weasyprint import HTML, CSS
from weasyprint.text.fonts import FontConfiguration

font_config = FontConfiguration()
css = CSS(
    string='''
    @font-face {
      font-family: "Report Sans";
      src: url("fonts/report-sans.woff2");
    }
    body { font-family: "Report Sans", sans-serif; }
    ''',
    base_url='/srv/report',
    font_config=font_config,
)

HTML(filename='/srv/report/index.html').write_pdf(
    '/srv/report/report.pdf',
    stylesheets=[css],
    font_config=font_config,
)

Reuse the same FontConfiguration for CSS objects applied to the document. If a glyph is missing, WeasyPrint may render the font’s .notdef glyph and log a warning; verify font coverage for multilingual documents.

@page {
  size: A4;
  margin: 18mm 16mm 20mm;
  @top-center { content: "Quarterly report"; }
  @bottom-right { content: counter(page); }
}

body {
  font-family: Arial, sans-serif;
  font-size: 10.5pt;
  line-height: 1.45;
}

h1, h2, h3 { break-after: avoid; }
table, figure { break-inside: avoid; }
.page-break { break-before: page; }

@media print {
  .screen-only { display: none; }
}

Use print media rules for page size, margins, running headers, page counters and break behavior. Build representative documents containing long tables, images, headings and footnotes, then inspect the actual PDF because pagination can expose interactions that are not visible in a browser.

Resources, URLs and authentication

Relative resources resolve against the document URL, the HTML <base> element, or the API/CLI base URL. The default URL support includes file, HTTP, FTP and data URLs. The default HTTP client does not support cookies or authentication. If a page requires credentials, provide a custom URL fetcher that adds headers or otherwise controls retrieval.

For production, make resource handling explicit:

  • Use absolute URLs or a known base URL.
  • Bundle critical CSS and fonts when practical.
  • Check every image, stylesheet and font URL in logs.
  • Set timeouts for network resources.
  • Restrict allowed protocols and redirects.

What WeasyPrint does not render like a browser

  • Interactive pseudo-classes such as :hover and :focus do not match in a generally non-interactive PDF.
  • JavaScript-dependent content should be generated before conversion.
  • Some right-to-left, bidirectional and table-related cases have documented limitations.
  • Browser-specific layout behavior is not a compatibility guarantee.

When fidelity depends on client-side JavaScript, wait-for-network-idle behavior, or a browser-only CSS feature, render with a real browser first or choose a browser-based capture service.

Security for untrusted HTML and CSS

The official security guide warns: “When used with untrusted HTML or untrusted CSS, WeasyPrint can meet security problems.” Untrusted documents can cause long render times, high CPU or memory use, or access to local files available to the rendering process. SVG must receive the same scrutiny because it uses the URL fetcher.

  1. Run the converter as a non-root user.
  2. Use a container or sandbox with a read-only, minimal filesystem.
  3. Limit CPU, memory and execution time.
  4. Restrict network access and URL protocols.
  5. Use a custom URL fetcher to allow only approved paths and hosts.
  6. Never expose secrets or private mount points to the renderer.

Read the official security and first-steps guidance before processing user-supplied markup.

Troubleshooting

Symptom Likely cause Fix
ModuleNotFoundError or shared-library error Missing Python or native dependency. Confirm Python 3.10+, install platform Pango packages, recreate the virtual environment, then run weasyprint --info.
Images or CSS are missing Relative URLs have no correct base. Pass base_url, use --base-url, add <base>, or switch to absolute URLs.
Remote asset returns 401/403 Default fetcher has no cookies or authentication. Use public resources or a custom URL fetcher with controlled headers.
Fonts show boxes or wrong glyphs Font file is unavailable or lacks characters. Check font URLs, pass one shared FontConfiguration, and verify coverage and warnings.
Layout differs from Chrome WeasyPrint is not a full browser and has documented CSS limits. Simplify print CSS, check support in the API reference, or render with a browser engine.
Conversion hangs or consumes too much memory Large or malicious input, slow resource, or unbounded layout. Set timeouts, cap resources, restrict fetching, isolate the process and reduce document size.
PDF fails an accessibility or archival validator Generation support is not a conformance guarantee. Validate the output separately and remediate structure, metadata and tags.
Output changes after an upgrade Rendering can change across major versions without an API break. Pin versions, compare representative PDFs, and review the changelog.

Performance, reliability and cost planning

  • Performance: Keep CSS and assets local where possible, avoid unnecessarily huge images, and split very large documents when your workflow allows it.
  • Reliability: Pin the WeasyPrint version, record warnings, test representative layouts, and compare PDFs after upgrades. WeasyPrint 70.0 was released on 2026-09-08 as a security update; review the changelog before deploying that or later versions.
  • Network dependence: Remote fonts, images and stylesheets add latency and can fail independently. Bundle critical assets or provide controlled retries outside the renderer.
  • Cost: WeasyPrint itself is free software under a BSD license. Your practical costs are compute, memory, storage, dependency maintenance and any isolated worker infrastructure.

Or skip the browser setup

If your goal is a PDF or image of a live URL and you do not want to manage rendering dependencies, ScreenshotNeo provides a single HTTP request. It can capture PDF with paper size, margins, landscape mode and page ranges, while also handling waits, custom headers, cookies, user agents, authorization, timezone and geolocation.

ScreenshotNeo removes cookie banners, newsletter popups and chat widgets before capture. Bot checks, blank pages, timeouts and failed loads are not billed, and the response reports the verdict in X-Page-Verdict and X-Billed headers. Its MCP server includes take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients.

See the ScreenshotNeo API documentation for all options.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -o shot.webp
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)
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 body = await res.arrayBuffer();

There are 1,000 free screenshots each month with no card. Paid plans start at $5 for 3,000 shots, and every feature is available on every plan. Create a free ScreenshotNeo account.

FAQ

Can WeasyPrint convert a web page that needs JavaScript?

Not reliably. Generate the dynamic content before conversion, or use a browser-based renderer for pages whose final layout depends on JavaScript.

Why are my relative images missing?

The document has no correct base URL. Supply base_url, --base-url, an HTML <base> element, or absolute resource URLs.

Does WeasyPrint guarantee PDF/A or PDF/UA compliance?

No. It can generate output intended for those standards, but you must validate the resulting file with a suitable validator.

Should I use WeasyPrint for untrusted HTML?

Only inside a restricted worker with limited filesystem, network, memory and CPU access, plus a URL fetcher that enforces an allowlist.

When should I choose ScreenshotNeo?

Choose it when you need a live website capture without installing a rendering stack, especially when consent banners, popups, chat widgets, failed loads or AI-agent access matter.