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

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

The reliable way to add images and links to a generated PDF is to choose the rendering model first: use an HTML/CSS renderer such as WeasyPrint when your template is naturally a web document, or use ReportLab when you need programmatic drawing and flowables. In either model, size images explicitly, resolve assets from a deterministic base or trusted fetcher, and test the PDF’s annotations in the viewers your readers use.

Choose the PDF authoring model

Images and links are not a single feature. A PDF can contain raster or vector artwork, external URI annotations, links to destinations in the same file, outline (bookmark) entries, and embedded attachments. The implementation details depend on whether your source is HTML/CSS or a programmatic document.

Concern WeasyPrint (HTML/CSS) ReportLab (programmatic)
Layout model HTML elements and CSS layout Flowables, paragraphs, canvas drawing and styles
Images <img>, <embed> and <object>; Pillow-supported PNG, JPEG and GIF plus SVG Image flowables or paragraph <img/> markup, with an explicitly configured source
External links Normal HTML <a href> Paragraph <a>/<link> tags and URI schemes
Internal navigation HTML fragment links and heading-derived bookmarks Named destinations and link annotations
Attachments rel="attachment" links or attachment link elements Use the PDF annotation and embedded-file APIs when you need packaged files
Best fit Existing web templates, semantic headings and CSS control Invoices, reports and graphics assembled from code and reusable flowables

Do not switch models halfway through a template without a reason. HTML/CSS keeps content maintainable; ReportLab gives direct control over drawing order, destinations and reusable form content.

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.

Adding images with WeasyPrint

Use supported formats and explicit dimensions

WeasyPrint accepts raster formats supported by Pillow, including PNG, JPEG and GIF, and can render SVG as vector artwork. SVG is useful for logos, diagrams and icons that must remain sharp at different zoom levels. Give every image a predictable width and height (or a constrained width with automatic height) so a large source file cannot unexpectedly change pagination.

from weasyprint import HTML

html = """
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <style>
      @page { size: A4; margin: 18mm; }
      .logo { width: 42mm; height: auto; }
      .photo { display: block; width: 120mm; height: auto; }
    </style>
  </head>
  <body>
    <img class="logo" src="assets/logo.svg" alt="Company logo">
    <h1 id="summary">Project summary</h1>
    <img class="photo" src="assets/photo.jpg" alt="Completed installation">
  </body>
</html>
"""

HTML(string=html, base_url="/srv/report-template/").write_pdf("report.pdf")

The base_url makes the relative assets/... references deterministic. In a web service, use an absolute template directory or a controlled URL-fetcher configuration rather than relying on the process’s current working directory.

Remote and authenticated assets

A remote image URL is fetched in the renderer’s context. That context may differ between a laptop, a container and a worker with no outbound network access. If an image requires authentication, provide it through a trusted fetcher or download it into a versioned local asset directory before rendering. Avoid unaudited URLs: they make builds nondeterministic and can expose credentials or internal network paths.

Images and pagination

Use CSS to preserve aspect ratio and control page breaks. A block image can be kept with its caption using break-inside: avoid where supported, but always inspect long captions and unusually tall images. Missing resources can leave an empty box or alter page flow, so treat missing-image detection as a build error in production.

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

External links, internal links and bookmarks in WeasyPrint

External URLs

A normal anchor becomes a clickable external link:

<p>Read the <a href="https://example.com/specification">specification</a>.</p>

Relative external links are resolved against the document’s base URL. The same HTML can therefore produce different targets if base_url or the URL-fetcher policy changes. Use absolute URLs for published documents when the destination must not depend on deployment location.

Same-document destinations and bookmarks

Give important sections stable IDs and link to them with a fragment:

<h2 id="terms">Terms and conditions</h2>
<p><a href="#terms">Jump to terms and conditions</a></p>

Semantic headings can also become PDF bookmarks. Keep heading levels logical; a skipped or overloaded hierarchy makes the outline difficult to navigate. Fragment names should be unique, short and stable across template revisions.

Attachments are not web links

An attachment travels inside the PDF instead of opening a webpage. WeasyPrint documents attachment relationships using an anchor or link element:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
<a rel="attachment" href="appendices/data.csv">Download the source data</a>

Use this for a supplementary CSV, JSON file or text note that should accompany the document. Explain to users that some viewers hide attachment panels by default and that browser PDF viewers may expose fewer attachment controls than desktop applications.

Inspecting generated links

WeasyPrint’s API exposes link records with a type (external, internal or attachment), a target and a page rectangle. Inspecting those records helps distinguish a styling problem from a missing annotation: if the rectangle and type exist, the PDF contains a link even if a particular viewer does not show a hover cue.

Adding images and links with ReportLab

Images in flowables and paragraphs

ReportLab paragraph markup supports an <img/> element with src, width and height, plus vertical alignment such as top, middle and bottom. The following program creates a document with an image, an external link and a same-document link:

from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import mm
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak

styles = getSampleStyleSheet()
doc = SimpleDocTemplate(
    "report.pdf", pagesize=A4,
    rightMargin=18 * mm, leftMargin=18 * mm,
    topMargin=18 * mm, bottomMargin=18 * mm,
)

story = [
    Paragraph('''<para>
      <img src="assets/logo.png" width="42" height="18" valign="middle"/>
      &nbsp;Project report
    </para>''', styles["Title"]),
    Spacer(1, 8 * mm),
    Paragraph('''<a name="details"/><b>Details</b> are listed below.''', styles["Heading2"]),
    Paragraph('Visit the <a href="https://example.com">project website</a>.', styles["BodyText"]),
    Paragraph('''<a href="#details">Return to details</a>''', styles["BodyText"]),
]

doc.build(story)

For remote images, configure trusted schemes and hosts explicitly. A source that works from a developer workstation may be rejected in a locked-down worker if its scheme or host is not permitted. Set link colors and typography intentionally so the affordance remains visible when a reader prints in grayscale.

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

Destinations and outline entries

When you need a generated table of contents or a bookmark tree, create named destinations and outline entries on the canvas. A custom canvas callback can call bookmarkPage("details") and addOutlineEntry("Details", "details", level=0) at the page where the destination is created. Link annotations can then target that destination with a #-style destination. Keep destination names unique, because a duplicate name can send every link to the wrong page.

Best Value
Sale
Sooez Architectural Templates, House Plan Template
  • Premium Quality : Made From Flexible, Yet Sturdy Material. Resilient and Convenient to Use
  • Set of 3 Architect Drawing And Interior Design Template Set (Scale: 1/4 Inch = 1 Ft): House Plan Template, Furniture Template, And Kitchen, Bed & Bath Template. Perfect For Architects, Builders, And Contractors
  • House Plan Template: Kitchen Appliances, Door And Electric Symbols, Plumbing Fixtures, And Roof Pitch Gauge
  • Furniture Template: Living Room, Dining Room, Bedroom, And Office Area Furnishings
  • Kitchen, Bed & Bath Template: Cabinets, Appliances, Beds, And Dressers

Reusable template graphics

For repeated logos, watermarks or decorative panels, ReportLab’s reusable form content can reduce duplicated drawing operations. Define the form once, then place it wherever the template needs it. This is especially useful for invoices, payslips and other documents with identical header and footer artwork.

Resolve and validate resources before deployment

  1. Set a base: pass an explicit base_url to WeasyPrint, or use absolute, controlled paths in ReportLab.
  2. Define trust: allow only the URL schemes and hosts required by the template; supply authentication through a controlled fetcher rather than embedding secrets in HTML.
  3. Version assets: keep local images and SVG files beside the template or in immutable object storage so a rebuild uses the same bytes.
  4. Validate dimensions: reject zero-byte files, unsupported formats and images whose pixel dimensions exceed your layout policy.
  5. Generate a link inventory: record every external, internal and attachment target before distributing the PDF.
  6. Test realistic viewers: open the file in at least one desktop viewer and one browser viewer, then test download, print and accessibility workflows.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If the image you need is a current webpage, capturing it through a browser stack can be more work than the PDF template itself. ScreenshotNeo returns a PNG, JPEG or WebP from one GET request, so you can save the result and place it into the WeasyPrint or ReportLab asset pipeline. Its consent handling removes cookie banners, newsletter popups and chat widgets before capture; bot checks, blank pages and failed loads are not billed. Responses identify the page verdict and billing status with X-Page-Verdict and X-Billed headers. An MCP server lets Claude, Cursor and other MCP clients call screenshot tools directly.

See the parameter reference in the ScreenshotNeo documentation. cURL:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Save the returned file as a versioned asset, check the response headers before adding it to the PDF, and keep the capture URL in your document’s provenance metadata. ScreenshotNeo includes 1,000 screenshots per month free with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

Troubleshooting generated PDFs

Symptom Likely cause Fix
Image area is blank Relative path resolved from the wrong directory, blocked host, unsupported format or failed authentication Set an explicit base URL, verify the fetch policy, convert to PNG/JPEG or SVG, and log the fetch response before rendering
Image is blurry Low-resolution raster scaled beyond its intended size Use a larger source or SVG; set physical dimensions instead of relying on pixel defaults
Link text appears but is not clickable The renderer did not create an annotation, or the viewer suppresses link cues Inspect WeasyPrint link records or ReportLab annotations, then test another viewer and check that the target scheme is allowed
External link opens the wrong host Relative URL resolved against an unexpected base Use an absolute URL or make the base URL part of your deployment configuration
Internal link jumps to the wrong section Duplicate or unstable IDs/destination names Generate unique names and keep them stable across template versions
Attachment is missing It was written as an ordinary URL or the viewer hides embedded files Use WeasyPrint’s attachment relationship or ReportLab’s embedded-file API, then verify in a viewer that exposes attachments
Bookmarks are absent Headings lack semantic structure or outline entries were never added Use a logical heading hierarchy in HTML, or call ReportLab outline methods at the destination pages
Layout changes between machines Remote assets, fonts, timezone or current working directory differ Pin local assets and fonts, pass explicit paths, and run the same renderer configuration in every environment

Performance, reliability and cost considerations

  • Fetch once, reuse many times: cache immutable images locally instead of downloading them for every PDF.
  • Control concurrency: remote asset servers and browser captures can fail under bursts; queue work and retry only idempotent fetches.
  • Keep output reproducible: record template version, asset checksums, renderer version and base URL with each generated file.
  • Separate failures: distinguish a missing image, a failed page load, a rejected link scheme and a viewer limitation in logs and user messages.
  • Budget by operation: PDF rendering consumes CPU and memory; remote capture, storage and bandwidth add separate costs. Measure your own workload instead of relying on generic benchmarks.
  • Protect secrets: never place API keys, cookies or Authorization values in a publicly downloadable PDF or in raw HTML sent to an untrusted renderer.

Final implementation checklist

  • Choose WeasyPrint for HTML/CSS semantics or ReportLab for direct programmatic control.
  • Use PNG, JPEG, GIF or SVG as appropriate and set image dimensions explicitly.
  • Define a deterministic base URL or trusted fetcher for every environment.
  • Implement external links, fragment links, bookmarks and attachments as separate features.
  • Use meaningful link text and stable anchor names.
  • Inspect annotations and destinations, then test download, print and accessibility paths in your target viewers.
  • For repeated template artwork, use reusable form content where the toolkit supports it.

Frequently Asked Questions

Can a PDF attachment be opened like a normal web link?

No. An embedded attachment is packaged inside the PDF and depends on the viewer’s attachment controls; an external link opens a URI outside the document.

Why can the same HTML produce different PDF links on two servers?

Relative URLs are resolved against the renderer’s base URL and fetch policy, so different deployment roots or URL-fetcher rules can change the final target.

Should a logo be PNG or SVG?

Use SVG when the source is vector artwork and the renderer/viewer pipeline supports it; use PNG or JPEG for raster photography or when the source is already raster.

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

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.