Private betav0.1.0
Docs
Format developer documentation

Assets

How Format bundles and resolves the images, fonts, and stylesheets your document references.

An assets bundle is a ZIP archive of the images, fonts, and stylesheets a document references. You send it alongside your document to the API, and the engine resolves each asset reference against it, so a render fetches nothing from the network. If your document references no external files, you don't need one.

What goes in the bundle

The bundle holds every file your document references by path:

  • Images: <img src> and CSS background-image: url(...).
  • Fonts: @font-face src: url(...).
  • Stylesheets: <link rel="stylesheet"> and CSS @import, plus the assets those stylesheets reference in turn.

Two things never become bundle entries: the document HTML, which sits alongside the bundle rather than inside it, and data: URIs, which carry their bytes inline.

Why asset bundles are required

At render time, Format will only read assets directly from your bundle – never from a remote URL. That's a deliberate constraint, for two reasons:

  1. Remote servers go down. If Format fetched assets at render time, an unreachable host would yield a document with missing images, broken layout, or substituted fonts. It would still produce a PDF, just not the one you intended.
  2. Renders stay reproducible. A remote file can be replaced or revised underneath you while the document stays the same. Two identical inputs would then produce a different PDF from one day to the next. A bundle pins the exact bytes, so a render you produce today reproduces identically months later.

Reading from a bundle you ship alongside the document makes every render deterministic, reproducible, and offline-safe.

How asset references resolve

Format places your document at the root of the bundle and serves the two together, like a website served from its root directory. Asset paths resolve against that root, exactly as they do in a browser.

Paths can be relative or absolute, and both forms resolve to the same file:

<img src="logos/brand.svg" />    <!-- relative to the root -->
<img src="./logos/brand.svg" />  <!-- the same file, written explicitly -->
<img src="/logos/brand.svg" />   <!-- absolute, also the root -->

A path maps straight onto the bundle's directory structure. Take a document with these references:

<img src="/logos/brand.svg" />
<img src="photos/team.jpg" />
<link rel="stylesheet" href="styles/invoice.css" />

They resolve against a bundle laid out like this:

logos
brand.svg
photos
team.jpg
styles
invoice.css

You may organize the directory structure however you like, as long as each reference matches the path of its file in the bundle.

A remote URL is the one reference that can't resolve. It points outside the bundle, so it fails the render with INVALID_ASSET_REFERENCE. To bundle a remote file deliberately, see Remote assets.

Useful

Automatic collection covers <img src>, CSS url(...), and <image>/<use> references inside inline SVG. It doesn't parse the contents of a referenced .svg file, so if a bundled SVG points at another file through its own <image href> or <use href>, add that file to the bundle yourself.

Creating the bundle

With Studio

Studio will automatically create your assets ZIP for each document when you run format compile. This is the right default for most documents. Assets are resolved once at build time, the ZIP is static, and there's no per-request work.

When a document's assets vary per request, you can use the --assets flag to get more fine-grained control, such as zipping at runtime, or building the assets ZIP yourself.

Without Studio

When you produce the document HTML another way (the React or Vue SDK, a server-side template, or by hand), build the bundle yourself with @format.dev/zip. This is what Studio uses under the hood. Its zip function scans the rendered HTML for the same references, resolves them against a directory you pass, and returns the ZIP bytes to send with the document.

import { renderToStaticMarkup } from 'react-dom/server'
import { zip } from '@format.dev/zip'
import { FormatClient } from '@format.dev/client'
import { Invoice } from './Invoice'

const html = renderToStaticMarkup(<Invoice data={data} />)
const assets = await zip(html, './assets')

const format = new FormatClient()
const response = await format.pdf(html, { assets })

This flow handles both static and dynamic assets, since you build the bundle at render time either way. When a document always references the same files, you can build the zip once and reuse it across renders instead of rebuilding it each time.

By default, zip rejects on the first missing file. Pass skipMissing: true to log and skip them instead.

Remote assets

By default, zip rejects remote asset references. When the HTML points at an http(s) URL and remoteAssets is not enabled, zip() throws a RemoteAssetsDisabledError naming each URL. A bundle without those files would produce a broken PDF, so the gap fails loudly at the source.

Set remoteAssets.enabled to fetch references over HTTP(S) at bundle-creation time and include the results instead:

const assets = await zip(doc.html, './assets', {
  remoteAssets: {
    enabled: true,
    headers: { Authorization: `Bearer ${token}` },
    timeoutMs: 15_000
  }
})

Your document still references the original remote URLs. When you build the bundle, each remote file is downloaded once and written into the ZIP alongside a manifest that maps every remote URL to its bundled file. At render time, Format reads that manifest and resolves each remote reference to its bundled copy, so nothing is fetched from the network.

Careful

Fetching remote assets reintroduces the failure mode local bundling exists to avoid. If a remote host is down or slow while you build the bundle, the build breaks or stalls. It's reasonable for a one-off bundle you build and inspect; avoid it on a production render path. Prefer copying the file into your project and referencing it locally.

Use headers for authenticated sources and timeoutMs to bound each request.

Remote assets in Studio

Studio handles remote references in three places:

  • Preview. Remote assets load in the preview so you can see your document, but Studio shows a warning naming each remote URL. The preview is more permissive than PDF generation, which never fetches remote references.
  • Downloads. PDFs downloaded from Studio fetch remote references automatically, so a document with a remote image downloads correctly.
  • Compile. format compile warns when your documents reference remote URLs. Pass --remote-assets to fetch them into each document's assets.zip at build time:
npx format compile --remote-assets

The flag defaults to off. With it on, the machine running compile needs network access to every referenced host. A fetch failure throws a RemoteAssetFetchError error. A common trap would be a CI runner that blocks outbound traffic.

Was this page helpful?