Tester UI
PDF Server ships a tester UI — a small React app served from a static
directory in the runtime image (at /static/tester/). It lets you pick a
template, provide data, and preview the rendered PDF interactively. It is the
fastest way to iterate on a template.
Open it
Browse to the server root:
http://localhost:9999/
(The root redirects to /static/.) The server logs the exact URL on startup.
What it offers
- Template picker — every loaded template and composition.
- Data input — paste JSON, or load one of the template's committed
examples/*.jsondatasets. - Live preview — renders the PDF and displays it inline.
- Schema & metadata — shows the template's JSON Schema and server metadata.
- Problems — what the browser noticed while rendering, when the server
runs with
--debug-enabled. - Layout — the printed pages with every measured box drawn over them, and the problems list next to them.
HTML preview
The HTML preview tab — shown only when the server runs with
--debug-enabled — shows the body, header and footer markup the template
engine produced for the data on the left, before any of it reaches the browser.
It is the fastest way to tell a template-engine problem from a print-layout
one.
Each part is shown one way at a time, switched by the Preview / Source toggle above them: Preview renders the markup in an isolated frame, Source shows it as text to read and copy. Both get the full width, because a rendering squeezed into half of it lays out nothing like the printed page.
The rendered body loads the template's own assets/ — stylesheets, images,
fonts — over the gated GET /_debug/templates/<name>/assets/<path> route, so it
shows the styled page rather than bare markup. The header and footer previews
deliberately do not: the browser gives them no access to assets/ when it
prints (see Header & footer), and a preview that loaded
them would show a page the printer will never produce. Scripts do not run
either — the frame is sandboxed with no privilege over the tester's origin,
because previewed markup is exactly where untrusted template output shows up.
A composition lists its child tasks instead; pick one to preview it, and use Back to task list to return.
Problems
The Problems tab — like the HTML preview, only when the server runs with
--debug-enabled — lists what the browser noticed while it rendered the data
on the left: assets that never arrived, scripts that threw, a font the image
does not have. Each row carries the severity as a coloured ! / ~ / .
chip, the kind, and a one-line detail; the list is ordered worst-first, then by
page, then in document order. Reading a render's
problems explains every kind.
The tab asks for a PDF-only bundle, because problems.jsonl is written
whatever the artifacts selector says — so opening it costs one render, not the
page images and HTML it never shows. Under
--templates-reload-mode=watch it re-renders when the template changes on
disk, like the preview beside it.
Layout
The Layout tab — --debug-enabled again — draws the
layout snapshot over the pages it was
measured from: every page is rasterized, and every measured box outlined on
top of it. Hovering a box names its selector, its size, and how far it
overflows; clicking one holds that. The problems list is the same list the
Problems tab shows, and selecting a problem scrolls to the element it is about
and flashes it, so a finding that reads as an abstract distance becomes a place
on a page. It sits to the right of the pages when the tab is wide enough for
both and moves under them when it is not — which depends on the split between
the input and preview panes, not on the size of the window.
Unlike the Problems tab this one asks for the PDF and the snapshot together —
the boxes have to be drawn over the render they were measured from, not over a
second one — so it is the most expensive tab to open, and it renders when you
open it rather than while it is closed. It re-renders on a template change
under --templates-reload-mode=watch, because an overlay left on the pre-edit
render does not look broken, it looks wrong.
Pages open scaled to the width available to them, never magnified past 100%,
and rescale when that width changes. The - and + buttons zoom between 25%
and 300% instead, and the third button returns to the fitted scale. A warning
beside them says when the snapshot's own pagination is approximate — the page
count it measured and the PDF's disagree, or the capture already knew its page
model was inferred — so an estimated box position is never read as a
measurement.
Overlay an example render
An example page carries the same tabs the render page does, so the overlay for a committed example is that page's Layout tab:
http://localhost:9999/static/tester/#/templates/hello/examples/basic?tab=layout
layout=shown was how an example page reached the overlay while it had no
tabs. It no longer exists, nor does the Overlay layout boxes on example
previews setting beside it; tab=layout is what both of them did.
With hot reload
Run pdf-server --templates-reload-mode=always server (the dev default) and each
render re-reads the template from disk, so you can edit the template file and
re-render in the tester without restarting. See the
author workflow.
Live watch
Run pdf-server --templates-reload-mode=watch server and the tester UI updates by
itself: the server watches the templates directory and pushes changes over
Server-Sent Events, so creating a template makes it appear in the menu and
editing the current one re-renders its preview — no page refresh. See
template store & hot reload
for how it works.
Collapsed sidebar
Open the tester with sidebar=collapsed to start with the template menu hidden —
handy for demos and embedding. The parameter works both before the anchor and
inside it:
http://localhost:9999/static/tester/?sidebar=collapsed
http://localhost:9999/static/tester/#/templates/hello/render?sidebar=collapsed
The menu toggle in the top bar still shows or hides the sidebar as usual.
Hide the PDF viewer toolbar
The preview iframes (on both the render and example pages) show the browser's
built-in PDF viewer, toolbar and all. Toggle Hide PDF viewer toolbar in the
settings popover — or open the tester with pdfToolbar=hidden — to strip that
chrome for a cleaner preview:
http://localhost:9999/static/tester/?pdfToolbar=hidden
http://localhost:9999/static/tester/#/templates/hello/render?pdfToolbar=hidden
The setting rides in the anchor query string like the sidebar one, and works before the anchor too, so it is shareable. It relies on the viewer's own parameters: Chromium hides its toolbar, while Firefox's pdf.js keeps its sidebar collapsed but still shows its top toolbar, which pdf.js offers no way to hide.
Open a preview tab from the URL
The preview area has tabs — PDF preview, Render Plan Preview, HTML preview,
Problems, Layout. tab= opens one directly, so "look at this render's
problems" is a link rather than a click someone has to be talked through:
http://localhost:9999/static/tester/#/templates/hello/render?tab=problems
http://localhost:9999/static/tester/#/templates/hello/examples/basic?tab=problems
http://localhost:9999/static/tester/?tab=html
Both pages have them: the render page previews the data in its editor, an
example page previews the committed examples/*.json data set it is named
for.
| Value | Tab |
|---|---|
pdf | PDF preview (the default, and what a clean URL opens) |
renderPlan | Render Plan Preview — only meaningful for a composition |
html | HTML preview |
problems | Problems |
layout | Layout |
Switching tabs by hand rewrites the parameter, so the URL in the address bar is always shareable. A value that names no tab is ignored and the PDF preview opens, which keeps a stale link from showing an empty page.
API spec viewers
The static assets also include OpenAPI viewers (Swagger UI, Redoc, Scalar)
served alongside the tester, pointed at the live /spec/swagger.json. See the
API reference.