October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PCOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
HTML to PDF

How to Convert HTML to PDF in Python with WeasyPrint

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

The shortest working conversion is: install WeasyPrint, import HTML, pass your markup with the named string= argument, and call write_pdf().

from weasyprint import HTML

HTML(string="<h1>Hello, PDF</h1>").write_pdf("output.pdf")

This guide shows the reliable versions of that pattern for strings, local files, remote URLs, CSS, images, fonts, in-memory responses, batch services, and untrusted input. The examples follow the official WeasyPrint 70.0 documentation, which lists Python 3.10 or newer and Pango 1.44 or newer as requirements. See the WeasyPrint first-steps guide and API reference for release-specific details.

Install WeasyPrint in an isolated Python environment

Use a virtual environment so the converter and its dependencies do not interfere with other projects:

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

WeasyPrint also depends on native libraries. The exact package names vary by operating system; Linux distributions commonly provide Pango and related graphics libraries through their package managers. A successful pip install does not guarantee that a deployment image has every native dependency. Install and verify the documented prerequisites on the target operating system, then pin the version used by your application.

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

For the documentation version covered here (70.0), use Python 3.10 or later and Pango 1.44 or later. Check the installed versions before deploying:

python --version
python -c "import weasyprint; print(weasyprint.__version__)"

Convert an HTML string to a PDF file

Use an explicit keyword argument for markup. A positional string can be interpreted ambiguously, while string= states that the value is HTML source:

from weasyprint import HTML

html = """
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Invoice</title>
    <style>
      @page { size: A4; margin: 18mm; }
      body { font-family: sans-serif; color: #222; }
      h1 { color: #155eef; }
    </style>
  </head>
  <body>
    <h1>Invoice 1007</h1>
    <p>Generated with Python and WeasyPrint.</p>
  </body>
</html>
"""

HTML(string=html).write_pdf("invoice.pdf")
print("Wrote invoice.pdf")

write_pdf("invoice.pdf") creates or replaces that file. The method accepts a filesystem path or a writable file object.

Choose the correct input form

Source Call When to use it
In-memory markup HTML(string=markup) Templates, generated reports, or database content.
Local document HTML(filename="report.html") An HTML file on the machine running Python.
Remote page HTML(url="https://example.com/report") A fully qualified HTTP or HTTPS address.

Use named arguments rather than relying on a positional value. A local path, URL, and literal markup have different resource-resolution behavior.

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

Make relative images, CSS, and fonts resolve

HTML often refers to assets such as css/report.css or images/logo.png. WeasyPrint must know the document’s base URL to resolve those paths. When generating a string, provide base_url:

from pathlib import Path
from weasyprint import HTML

root = Path(__file__).parent.resolve()
markup = """
<html>
  <head><link rel="stylesheet" href="css/report.css"></head>
  <body><img src="images/logo.png" alt="Company logo"></body>
</html>
"""

HTML(string=markup, base_url=str(root)).write_pdf("report.pdf")

An HTML <base> element can set the document base as well. If you load a file with filename=, its directory normally supplies the natural context; explicitly setting a base is still useful when templates and assets live in different directories.

Return PDF bytes instead of writing a file

Omit the target argument to receive PDF bytes. This is useful for an HTTP response, object storage upload, or a queue message:

from weasyprint import HTML

pdf_bytes = HTML(string="<h1>Download me</h1>").write_pdf()

with open("download.pdf", "wb") as output:
    output.write(pdf_bytes)

The API also accepts an open binary file object:

with open("download.pdf", "wb") as output:
    HTML(string="<p>Saved through a file object</p>").write_pdf(output)

Control pagination with print CSS

WeasyPrint uses print media by default. Put page geometry and print-specific rules in CSS:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
@page {
  size: Letter portrait;
  margin: 20mm 16mm 22mm;
}

@page :first {
  margin-top: 10mm;
}

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

Use @page for paper size and margins rather than browser zoom. The API exposes rendering options, user stylesheets, and CSS objects or stylesheet paths. Changing zoom casually changes the physical size of CSS units and can alter pagination.

Test long tables, headings near page bottoms, widows and orphans, and images that approach page boundaries. WeasyPrint is a paginated HTML/CSS renderer, not a full browser engine, so browser-only layout behavior is not a compatibility guarantee.

Fonts, international text, and images

Fonts available through the system’s font configuration can be embedded and are subset by default. Install the required fonts in the actual runtime image, not only on a developer workstation, and test glyph coverage for languages you support. For custom @font-face rules, pass a shared FontConfiguration when constructing CSS and rendering, as described in the API reference.

Use absolute or correctly based image URLs. Missing images and stylesheets can otherwise produce a PDF that appears valid but is visually incomplete. Inspect warnings and decide whether an absent asset should fail the job.

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

Generate a PDF from a remote URL

from weasyprint import HTML

HTML(url="https://example.com/invoice/1007").write_pdf("remote.pdf")

The default fetcher supports file and HTTP URLs, but its HTTP client does not provide advanced cookie or authentication handling. If a page needs credentials, implement a custom URL fetcher that supplies the required headers or session behavior. Restrict that fetcher to approved protocols and hosts.

Build a safer conversion service

The WeasyPrint documentation warns: “Using WeasyPrint with untrusted HTML or untrusted CSS may lead to various security problems.” Rendering can consume excessive CPU or memory, and resources may reach files or network locations accessible to the process.

  • Run conversion as a non-root user in a container or separate worker.
  • Apply CPU, memory, wall-clock, and output-size limits.
  • Restrict URL schemes and allowed hostnames; reject unexpected file paths.
  • Use a custom fetcher for controlled network access and authentication.
  • Treat SVG and embedded resources as untrusted input.
  • Keep user content away from private metadata, credentials, and internal services.
  • Capture warnings and make missing required assets an explicit application error.

For trusted, internally generated templates, these controls can be lighter, but the trust boundary should be deliberate.

Batch rendering and performance

For occasional documents, a direct call is sufficient. For many documents, keep a long-lived Python process and reuse your application setup instead of starting a new process for every PDF; this is the approach suggested by the first-steps documentation, not a published benchmark. Queue jobs, cap concurrency according to available memory, and write output to a temporary file before atomically moving it into place.

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.

Reduce avoidable work by limiting oversized images, selecting only the fonts you need, and avoiding remote assets that can stall a job. Set an application-level timeout because a network resource or pathological document may not finish promptly.

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

Troubleshooting common failures

ImportError or native-library errors

Cause: a missing or incompatible system dependency. Fix: install the native packages listed for your operating system, verify Python and Pango versions, and rebuild the deployment image.

PDF has no logo, CSS, or web fonts

Cause: relative URLs have no usable base, or the runtime cannot fetch the resource. Fix: pass base_url, use a valid absolute URL, install the font, and inspect WeasyPrint warnings.

Markup is treated like a filename

Cause: ambiguous positional input. Fix: call HTML(string=markup); use filename= or url= for those source types.

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

Authenticated page is incomplete

Cause: the default HTTP fetcher does not handle advanced cookies or authentication. Fix: provide a restricted custom URL fetcher or render authenticated content into a controlled local template.

Pages break in surprising places

Cause: print CSS and browser CSS are not identical, or content cannot fit the available page area. Fix: define @page, use break-before/break-inside deliberately, and test representative documents and tables.

Conversion hangs or consumes excessive resources

Cause: expensive markup, huge assets, or an unreachable resource. Fix: enforce time and memory limits, restrict fetching, cap input and image sizes, and isolate the worker.

Or skip the browser setup

If your real requirement is to capture a live web page as a PDF rather than render your own HTML, ScreenshotNeo provides a website screenshot API and MCP server. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; failed loads, bot checks, blank pages, timeouts, and cache hits are not billed. Its MCP server includes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.

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

One GET request can return a PDF:

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

See the ScreenshotNeo API documentation for PDF options and authentication. There is a free allowance of 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. If that fits your workflow, sign up for ScreenshotNeo.

Validate a production PDF

  • Open the output with a PDF parser or viewer and verify that it is non-empty.
  • Check page count, expected headings, text extraction, links, images, and font glyphs.
  • Render a sample page to an image in CI to catch visual regressions.
  • Test the largest expected document, not only a one-line example.
  • Record the WeasyPrint, Python, native-library, and font versions used for each deployment.

Frequently Asked Questions

Does WeasyPrint require a web browser such as Chrome?

No. Its documented Python API renders HTML and CSS directly; browser automation is not required.

Can I convert a Python string directly to PDF bytes?

Yes. Call HTML(string=markup).write_pdf() without a target and use the returned bytes.

Why is my PDF different from the page in Chrome?

WeasyPrint is a print-focused paginated renderer, not a full browser engine. Use print CSS and test features that matter to your layout.

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.

What is the safest way to render user-submitted HTML?

Isolate the worker, restrict filesystem and network access, enforce resource limits, and use a URL fetcher that allows only approved protocols and hosts.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

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

Read next

Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Outdated Drivers Are Slowing You DownFree scan - exact matches

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.