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.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware match#1 Best Overall
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.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →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:
Rank #2
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:
@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.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →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.
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.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.
Best Value
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.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsOne 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.
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.
Quick Recap
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.




