Free tools Windows power users keep installed
One-click scans. No signup required.
Use an absolute stylesheet URL or a correct base URL, then let the PDF engine fetch it. With WeasyPrint, the usual pattern is HTML(..., base_url=...), CSS(url=...), and HTML.write_pdf(). Relative images, fonts, and nested imports resolve from that base. If the stylesheet is protected by cookies or authentication, the default HTTP fetcher is not enough: fetch the CSS yourself or provide a custom URL fetcher that adds the required credentials.
Load a remote stylesheet with WeasyPrint
WeasyPrint accepts absolute URLs for both HTML and CSS resources. This is the smallest working example when the stylesheet is public:
from weasyprint import HTML, CSS
html_text = """
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Invoice</title>
</head>
<body>
<h1>Invoice 1042</h1>
<p>This page is rendered with a remote stylesheet.</p>
</body>
</html>
"""
html = HTML(string=html_text, base_url="https://example.com/")
remote_css = CSS(url="https://cdn.example.com/print.css")
html.write_pdf("invoice.pdf", stylesheets=[remote_css])
The URL fetcher retrieves external resources such as CSS stylesheets and images while rendering. Keep the CSS URL absolute whenever possible; it removes ambiguity about the scheme, host, and path.
When the HTML already has a link element
You can leave the stylesheet in the document instead of passing a separate CSS object:
#1 Best Overall
from weasyprint import HTML
html_text = """
<html>
<head>
<link rel="stylesheet" href="https://cdn.example.com/print.css">
</head>
<body><h1>Report</h1></body>
</html>
"""
HTML(string=html_text, base_url="https://example.com/").write_pdf("report.pdf")
An absolute href works without a base URL for that stylesheet. A base URL is still important for relative images, fonts, CSS @import rules, and other assets in the HTML.
Resolve relative images, fonts, and imports
Relative URLs are interpreted from the document’s origin. If you build HTML from a string, there is no origin unless you supply base_url.
from weasyprint import HTML, CSS
html_text = """
<html>
<head>
<link rel="stylesheet" href="css/print.css">
</head>
<body>
<img src="images/logo.png" alt="Company logo">
</body>
</html>
"""
HTML(string=html_text, base_url="https://www.example.com/app/").write_pdf(
"report.pdf"
)
Here, css/print.css resolves under https://www.example.com/app/, and the image resolves under the same base. If the HTML came from a local file, use the directory containing that file as the base instead of an unrelated website URL.
Assets referenced inside CSS
Fonts and background images inside the remote stylesheet have their own relative URLs. A stylesheet at https://cdn.example.com/css/print.css will normally resolve url(../fonts/report.woff2) relative to that stylesheet’s location. Verify that those URLs are reachable under the same fetch policy. For custom fonts, pass one shared FontConfiguration to both the CSS and PDF render:
from weasyprint import HTML, CSS
from weasyprint.text.fonts import FontConfiguration
font_config = FontConfiguration()
css = CSS(
url="https://cdn.example.com/css/print.css",
font_config=font_config,
)
HTML(
string="<h1 class='brand'>Quarterly report</h1>",
base_url="https://www.example.com/",
).write_pdf(
"report.pdf",
stylesheets=[css],
font_config=font_config,
)
Send cookies or authentication headers
The default WeasyPrint fetcher can open file and HTTP URLs, but its HTTP client does not provide advanced features such as cookies or authentication. A protected CSS endpoint may therefore return a login page, a redirect, or a 401/403 response instead of CSS.
Rank #2
Option 1: prefetch CSS with your HTTP client
Download the stylesheet with the credentials your application controls, then pass its text to CSS(string=...). This is often easiest to debug because you can inspect status, redirects, and the returned content before rendering.
import requests
from weasyprint import HTML, CSS
session = requests.Session()
session.headers.update({"Authorization": "Bearer YOUR_TOKEN"})
session.cookies.set("session", "YOUR_SESSION_COOKIE", domain="example.com")
response = session.get("https://example.com/private/print.css", timeout=30)
response.raise_for_status()
css = CSS(
string=response.text,
base_url="https://example.com/private/",
)
HTML(
string="<h1 class='invoice-title'>Private invoice</h1>",
base_url="https://example.com/",
).write_pdf("private.pdf", stylesheets=[css])
Set base_url on the in-memory CSS so relative url() and @import references continue to resolve. If imported resources also require credentials, they must be fetched through a policy that supplies those credentials.
Option 2: implement a custom URL fetcher
A custom URLFetcher can attach headers or cookies whenever WeasyPrint requests CSS, images, fonts, or imports. Keep the implementation limited to approved hosts and return the response body, MIME type, and encoding expected by the fetcher interface. Log status codes and final URLs while diagnosing failures, but never log bearer tokens or session cookies.
Use a custom fetcher when many resources share the same authentication policy. Prefetching is preferable when you need explicit caching, retries, response validation, or different credentials for different resources.
Check that the response is really CSS
Before changing your PDF code, request the stylesheet directly. Confirm the final response status, content type, and a body that begins with CSS rather than an HTML sign-in page. Also check redirects, TLS certificate validation, DNS, and firewall rules from the machine running Python; a URL that works in your browser may be inaccessible from a server or container.
- Use an absolute URL for the stylesheet and any security-sensitive asset.
- Inspect the final URL after redirects; relative imports may resolve from a different host than expected.
- Confirm that fonts and images referenced by the CSS return the expected bytes.
- Read WeasyPrint warnings: unsupported CSS can be a rendering limitation rather than a network failure.
xhtml2pdf: use path and link_callback
If your project uses xhtml2pdf, its path argument establishes the original file path or URL used for relative resources. A link_callback can rewrite each URI to a local file or a downloaded resource.
from xhtml2pdf import pisa
html_text = """
<html>
<head>
<link rel="stylesheet" href="css/print.css">
</head>
<body><img src="images/logo.png"></body>
</html>
"""
with open("report.pdf", "wb") as target:
result = pisa.CreatePDF(
html_text,
dest=target,
path="https://example.com/reports/",
link_callback=my_link_callback,
)
if result.err:
raise RuntimeError("xhtml2pdf could not create the PDF")
Your callback receives a URI (and, depending on the version and call, a base path) and returns a resource location. Use it to download authenticated CSS with your own HTTP client, map approved remote URLs to cached files, or reject hosts that should never be contacted.
CLI input from standard input
When using the xhtml2pdf command-line interface with HTML piped on standard input, set --base so relative links have an origin. Use --allow-host to restrict remote fetching, or --no-remote to disable HTTP and HTTPS access entirely.
Why a remote stylesheet does not apply
The CSS URL returns HTML
Authentication middleware commonly returns a login document with a successful HTTP status. Inspect the response body and content type, then add the required cookie or authorization header through prefetching or a custom fetcher.
Relative URLs have no base
HTML created with HTML(string=...) needs base_url. xhtml2pdf needs path. Set the base to the directory or URL that actually owns the document, not merely the CDN host.
Redirect, TLS, DNS, or firewall failure
Follow redirects and test from the same runtime, container, or worker that generates the PDF. Fix certificate trust, DNS, proxy, or outbound firewall configuration before changing CSS.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Fonts or images are missing
Open every URL referenced by url(), @font-face, and HTML tags under the renderer’s credentials. A stylesheet can load successfully while one of its dependent resources is blocked.
Browser-only CSS is unsupported
PDF engines do not implement every browser feature. Replace unsupported rules with print-oriented CSS and check renderer warnings. Do not diagnose an unsupported property as a failed download.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Reliability, performance, and caching
Remote resources add network latency and another failure point. In production, set explicit HTTP timeouts, validate status and content type, and retry transient failures in your own fetcher. Cache immutable CSS and font files where appropriate, but invalidate the cache when a deployment changes the stylesheet. Keep a record of the stylesheet version or response hash alongside generated PDFs when reproducibility matters.
For large documents, avoid downloading the same font or image repeatedly. A shared session, bounded cache, and connection reuse reduce work without allowing untrusted URLs to expand indefinitely.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Best Value
Security boundaries for remote HTML and CSS
WeasyPrint warns that untrusted HTML or CSS can create security problems, including local-file access and expensive or endless rendering. Treat every URL and stylesheet as input at a trust boundary.
- Run rendering in a sandbox with least-privilege filesystem and network access.
- Allowlist schemes and hosts; deny unintended
file://access. - Limit rendering time, memory, document size, and the number of fetched resources.
- Do not forward application secrets to arbitrary URLs.
- Use a restricted callback or URL fetcher for multi-tenant workloads.
Or skip the browser setup
If your actual requirement is a clean image or PDF capture of a public web page rather than Python-controlled HTML rendering, 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; bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP tools let Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf.
One GET request is enough:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo documentation for options such as full-page capture, CSS-selector element capture, custom CSS and JavaScript, cookies and headers, waiting for selectors or network idle, PDF paper settings, and signed webhooks. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
FAQ
Can I use a relative stylesheet URL in a Python string?
Yes, but only after assigning the document’s real origin with WeasyPrint’s base_url or xhtml2pdf’s path. Without that origin, the renderer cannot know which directory the relative URL belongs to.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Should I pass CSS through CSS(url=...) or keep a <link> tag?
Both paths are supported. Passing CSS(url=...) makes the stylesheet explicit in Python; a link tag keeps document structure closer to browser HTML. Choose one approach per stylesheet to avoid accidental duplication.
How do I prevent a tenant’s HTML from reaching internal services?
Use an allowlist-based URL fetcher or xhtml2pdf callback, disable local-file access, and enforce time, memory, host, and resource-count limits before rendering.
Frequently Asked Questions
Can I use a relative stylesheet URL in a Python string?
Yes, after setting the document origin with WeasyPrint’s base_url or xhtml2pdf’s path; otherwise the renderer has no directory from which to resolve it.
Should I pass CSS through CSS(url=...) or keep a <link> tag?
Both are supported. Use the Python argument when you want rendering configuration in code, or the link tag when you want the HTML to remain self-contained.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallHow do I prevent untrusted HTML from reaching internal services?
Use an allowlist-based fetcher or callback, block local-file access, and enforce host, resource-count, time, and memory limits.
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.

