Overview
PDF Server turns predefined HTML templates into PDFs using a real headless browser — so you build a PDF microservice by writing templates, not code.
What it is
PDF Server is a small HTTP service written in Go. You give it a directory of templates; it exposes an HTTP API that renders those templates — filled with request data — into PDF files. Rendering is done by a pooled headless browser (Chromium or Firefox) driven over the Chrome DevTools Protocol (CDP), so the PDF is exactly what a modern browser would print: real CSS, web fonts, SVG, flexbox, grid, and JavaScript.
The intended workflow is three steps:
- Create a template — a directory with an HTML template and optional schema, PDF parameters, assets, partials, and examples.
- Build your image — add your
templates/on top of the PDF Server base Docker image. - Run your service — start the container and
POSTJSON to render PDFs.
You never fork or modify PDF Server to add a document — you add a template. This splits cleanly into two phases:
- Build time (once): you author templates and assemble them on top of the
pdf-serverbase image (FROM … / COPY templates …) to produce your service image. - Run time (every request): your application
POSTs JSON; PDF Server validates it, renders your template, prints with the browser, and returns the PDF — all inside your own infrastructure, so the data never leaves your network.
What problem it solves
Rendering HTML with a real browser is the reliable way to get full CSS fidelity, and PDF Server is not the only tool that does it. What it removes is the work around the browser — the parts that usually turn “print an invoice” into a project of its own:
- Write documents as plain HTML/CSS. There is no rendering code to write and no browser automation to script; a template is just a folder of files.
- Test and debug them locally in the built-in tester UI — pick a template, edit the data, and watch the PDF re-render on every save, on your own laptop.
- Deploy by copying your
templates/on top of the base image. That is oneCOPYin aDockerfile; there is no build pipeline for the service itself, because you are not building a service — you are adding templates to one.
You still own and operate the whole thing inside your own network, so your data never goes to a third party. The difference is that you get there without writing, wiring up, or maintaining a PDF renderer.
When to use it
PDF Server fits well when you need a document service: invoices, receipts, reports, tickets, labels, certificates, contracts. It is a particularly good fit when:
- your documents are best expressed as HTML/CSS (which is most of them);
- you want a single self-contained image to deploy on your own infrastructure;
- you want data validation, headers/footers, and multi-document composition without writing rendering code.
It is not a general-purpose “convert any URL to PDF” gateway: templates are authored and trusted; only the data filling them is treated as potentially untrusted (see Security model).
How a render flows
At run time, data becomes a PDF in a few clear stages — validate, render, print, and (for compositions) merge:
flowchart TD
A["Your app / service"] -->|"POST JSON data"| V
subgraph C["Your PDF Server container"]
direction TB
V["1 · Validate<br/>against schema.json"] --> R
subgraph Pass["2–4 · Render pass — one per part, run concurrently for a composition"]
direction TB
R["Render HTML<br/>template + data + assets"] --> S["Serve HTML<br/>internal loopback server"]
S --> P["Print to PDF<br/>pooled headless browser"]
end
P --> M["5 · Merge sub-render PDFs<br/>into one document"]
end
M -->|"application/pdf"| AA POST /templates/<name>/render request travels through the service like this:
- The HTTP handler looks up the template by name.
- If the template has a
schema.json, the request data is validated against it (invalid data →422, no PDF). - The template’s body/header/footer are rendered to HTML by the chosen engine.
- The HTML is stored and served by a small loopback-only internal server.
- A browser is borrowed from the pool and navigates to that HTML.
- Once the configured lifecycle event fires (default
load), the browser is asked to print to PDF. - The PDF bytes are returned as
application/pdf.
For compositions, steps 3–6 run once per part. The parts are scheduled to render concurrently — how many at a time is bounded by the browser render pool (RENDER_POOL_MAX_TOTAL, 1 by default, so enlarge the pool to actually render parts in parallel) — and their PDFs are merged into a single document before step 7.
Next steps
- Quickstart — run it and render your first PDF in minutes.
- How rendering works — the full render path: parallel rendering, compositions, lifecycle timing, and asset serving as one machine.
- Template author workflow — the day-to-day dev loop.
- Your first template — anatomy of a template.
- Build your image — package templates into your own image.
- Architecture — for contributors extending the service.