Templates / Fonts & images

Fonts & images

Templates can use fonts and images either embedded in the HTML or fetched from a URL at render time. Both work; embedding is more reliable.

Two ways to include an asset

A data: URI carries the asset inside the document, so nothing is fetched over the network. This is the most reliable option: the render cannot fail on a slow or unreachable host, and the output is identical on every machine.

<img src="data:image/png;base64,iVBORw0KGgoAAAANS..." alt="Logo" />

<style>
  @font-face {
    font-family: 'Inter';
    src: url('data:font/woff2;base64,d09GMgABAAAA...') format('woff2');
  }
</style>

Reference it by URL

You can also point at an external image, stylesheet, or font:

<link rel="stylesheet" href="https://fonts.example.com/inter.css" />
<img src="https://cdn.example.com/logo.png" alt="Logo" />

External URLs are fetched server-side during the render, which is why the rules below apply.

Supported formats

  • Images: PNG, JPEG, GIF, WebP, and SVG (Chromium also handles AVIF, ICO, and BMP).
  • Fonts: WOFF, WOFF2, TTF, and OTF, via @font-face or a linked stylesheet.

The render waits for sub-resources to load before printing, so images and fonts are present in the output rather than half-loaded.

Rules for external resources

Because external URLs are fetched by our servers, a few rules keep that safe and bounded:

  • Public HTTP(S) only. Each URL's host is resolved, and every resolved address must be public. Private, loopback, link-local, and cloud-metadata addresses are blocked, and only http/https is allowed. Redirects are re-checked at every hop, so a public URL cannot redirect to a private one.
  • A 10-second timeout per resource. Any single font or image that takes longer than 10 seconds to fetch fails the render. The whole render also has a 30-second budget.
  • One bad resource fails the call. If a resource is blocked, unreachable, or times out, the generation fails with render_failed (502) and the error message names the offending URL, so you know exactly what to fix. A failed call does not consume quota.

These rules are why embedding matters for fidelity: a data: URI never touches the network, so it cannot be blocked, cannot time out, and renders the same everywhere. For documents that must be byte-for-byte reproducible, inline your CSS and embed your assets so there is nothing to fetch.

A reliable setup

For production templates, the most robust pattern is:

  1. Inline your CSS in a <style> tag rather than linking a stylesheet.
  2. Embed logos, signatures, and icons as data: URIs.
  3. Embed fonts with @font-face and a data: src, or rely on the common system fonts the renderer already has.

This removes every external dependency from the render path, which is the surest way to keep the preview, your first call, and your millionth call identical.