Template structure

Every template is a directory loaded from your templates folder (--templates-dir, default templates/). Its name — the directory path — is the name you use in the URL.

Files

FilePurposeRequired
template.<ext>Body template. Extension selects the engine.Yes*
header.<ext>Running page header.No
footer.<ext>Running page footer.No
schema.jsonJSON Schema validating request data.No
params.jsonPDF print options.No
examples/*.jsonNamed example datasets.No
assets/…Static files served to the browser as ./assets/....No
partials/…Reusable template fragments.No

* A directory can instead be a raw template (raw.pdf), a remote template (template.http.hbs), or — under the compositions directory — a composition.

Loading order (which loader wins)

For each directory, loaders are tried in priority order and the first match wins:

  1. File templatetemplate.<ext> (HTML → PDF via a browser).
  2. Raw templateraw.pdf (returned as-is).
  3. Remote templatetemplate.http.hbs (fetched from another service).

Compositions are loaded separately from the compositions directory (--compositions-dir, default compositions/).

Engine priority

When several body files exist, the engine is chosen in this order: Mustache → Handlebars → Go template → static HTML. See Engines.

Nested templates

With --templates-recursive, templates can be nested in subdirectories. A template at templates/invoices/eu/ is named invoices/eu (URL-escaped as invoices%2Feu). All children of a directory must themselves be directories.

Names in URLs

Template names are URL-path components, so escape special characters:

  • nested: invoices/eu/templates/invoices%2Feu/render
  • composition: addressed as composition:<name>/templates/composition%3A<name>/render

Hot reload

With --templates-reload-mode=always, each request re-reads the template from disk, so edits take effect without a restart — ideal for local authoring. In production use the default none for a static, validated-at-startup store.

See also: Data validation, PDF params, Assets, Partials.