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.