The document is wrong
Images do not appear in the PDF
Text renders correctly, layout is right, and every image is either a blank space of the correct dimensions or a broken-image placeholder. Logos and product photos are the usual casualties.
What is actually happening
The image src is a path rather than a URL. In the browser the path resolves against the page's origin, which the user's session already has. The renderer has no origin, so /static/logo.png points at nothing. The second cause is an image behind authentication, which the renderer cannot present a cookie for.
Confirming it is this and not something that looks like it
Search the HTML string you are sending for src=" and look at what follows. Anything starting with a slash or a dot rather than https:// is the problem.
The fix
Use absolute URLs on a publicly reachable host, or embed the image as a data URI. Data URIs are the more reliable of the two because they remove the network from the equation entirely, at the cost of request size. For logos and other small fixed assets, embed them; for user-uploaded photos, use absolute URLs to your object storage.
import base64
from pathlib import Path
def data_uri(path: Path) -> str:
"""Small, fixed assets belong in the markup, not behind a URL the renderer
would have to authenticate against."""
encoded = base64.b64encode(path.read_bytes()).decode()
suffix = path.suffix.lstrip(".")
return f"data:image/{suffix};base64,{encoded}"
context = {
"logo": data_uri(Path("assets/logo.png")),
# User content stays a URL: embedding it would blow up the request.
"photo_url": storage.public_url(order.photo_key),
}If that was not it
These produce the same symptom often enough to be worth ruling out before assuming the fix above did not work.
- loading="lazy" on images below the first screen, which never enter the viewport during a render
- Images served from a host that blocks requests without a Referer or a browser User-Agent
- SVG referenced through an img tag with its own external stylesheet, which is not fetched
- Signed URLs that expired between generating the HTML and rendering it
Frequently asked
Does this happen with every rendering engine?
The behaviour behind it is not specific to one tool. The image src is a path rather than a URL. Anything rendering HTML to a paged medium has to make the same decision, so the fix travels with you if you change how the render happens.
Will this show up as an error in my logs?
No, and that is what makes it expensive. A document that renders wrong is still a successful render as far as every log line is concerned. It is found by someone opening the file, which is usually the customer.
Related failures
Problems people arrive at from the same starting point, or mistake for this one.
Background colours and images are missing from the PDF
Browsers drop backgrounds when printing, because a page with every background painted would empty a toner cartridge, and most render APIs inherit that default.
SVG images are missing or wrong in the PDF
There are three different problems wearing one symptom.
The generated PDF is blank
Almost always one of three things: the content is rendered by JavaScript that had not run when the snapshot was taken, the body has no height because everything inside it is absolutely positioned or floated, or the markup you sent was empty because the template call returned before the data arrived..
The PDF ignores my CSS
The stylesheet is linked rather than embedded, and the link points somewhere the renderer cannot reach: a bundler-generated path, a digested asset filename, or a URL behind your application's authentication.
Content rendered by JavaScript is missing from the PDF
The snapshot was taken before the script finished.
CSS for paged documents, and what actually works
Which parts of the paged media specification a browser-based render implements.
Everything that goes wrong, by category
The full list, grouped by where in the pipeline it breaks.
Paste your markup and see the rendered document, without signing up.