Skip to main content

Template author workflow

You use PDF Server as a base image: you write templates and schemas, test them, and ship your own image. Here is the development cycle that supports that.

1. Scaffold a template

Create a directory under your templates/ folder. The minimum is a body template; add schema, params, assets, partials, and examples as needed.

templates/
invoice/
template.handlebars # body (required)
schema.json # validate request data
params.json # PDF options (margins, format, …)
examples/
sample.json # example dataset for the tester + CI

See Your first template and Template structure.

2. Iterate with hot reload + the tester UI

Run the server against your templates with reload mode always so edits take effect without a restart:

docker run --rm -it -p 9999:9999 \
-e TEMPLATES_RELOAD_MODE=always \
-v "$PWD/templates:/src/templates" \
registry.gitlab.com/c0va23/pdf-server:latest-chromium

Then open http://localhost:9999/ and use the tester UI: pick your template, paste or load example data, and preview the rendered PDF. Edit the template file, re-render — the change is live. See Tester UI.

always re-reads templates on each render; watch additionally pushes live updates to the tester UI so it refreshes on its own. See Template reload modes for the trade-offs and when to use each.

The examples/*.json datasets you commit show up in the tester and are what CI renders, so they double as fixtures and documentation.

3. Check what actually came out

The preview tells you the render succeeded. It does not tell you the page is right — a line that runs off the paper, a table split across a break, a font that quietly fell back all render perfectly happily. debug render answers that question for one template and one data set:

pdf-server \
--templates-dir ./templates \
debug render --template invoice --example sample --artifacts pdf,png

It prints a short report — one line per problem, ok pages=1 no problems when there is nothing to say — and writes a bundle under .pdf-debug/:

.pdf-debug/invoice/sample/
document.pdf
page-001.png # a real raster of the printed page, one file per page
problems.txt
problems.jsonl
manifest.json

--artifacts png is the one worth knowing about: page-001.png is the printed page rasterized, so you (or an agent working on your templates) can look at what the browser produced instead of inferring it from an exit code. Open it like any image.

The exit code is the CI half of the same answer: 0 clean, 1 a problem reached --fail-on, 2 the check could not run at all. See Reading a render's problems for every kind it reports, and CLI & configuration for the flags.

4. Validate before you ship

The validate subcommand parses every template and renders every example, exiting non-zero on any failure. Wire it into CI so a broken template fails the build:

pdf-server \
--templates-dir ./templates \
--compositions-dir ./compositions \
validate

Filter to a subset while iterating:

pdf-server validate invoice/sample

validate answers one question — does every example still render — and it is not the whole gate. It reports no diagnostics and runs no layout rules, neither the built-in ones nor any a template ships in its diagnostics/ directory, so a template whose totals block splits across a page still passes it. That check is debug render above, over the same examples:

pdf-server --templates-dir ./templates \
debug render --template invoice --all-examples --fail-on warn

5. Build your image

Layer your templates onto the base image with a short FROM … / ADD templates Dockerfile. See Build your image for the recipe and the full deployment story (recursive templates, compositions).

6. Deploy and operate

Run the image on your infrastructure behind your own auth/proxy. Tune the browser pool, timeouts, and factory mode for your load, and turn on tracing if you use OpenTelemetry. See Operations and Tuning.

The loop, in short

scaffold → run with reload + tester UI → edit/preview → check the page → validate → build image → deploy

Everything except the final deploy runs locally with a single container and no rebuilds.