Skip to main content

Lifecycle events

PDF Server waits for a page lifecycle event before printing to PDF. By default it waits for load — all synchronous resources are loaded and rendered. For pages with async content (charts, fetch, web fonts), you can wait longer.

Choosing the event

Set it per template in params.json or globally with --wait-lifecycle-event / WAIT_LIFECYCLE_EVENT:

{ "pdf": { "waitLifecycleEvent": "networkIdle" } }

Available events

EventFires when…
initnavigation begins
DOMContentLoadedHTML parsed, before subresources
loaddefault — all sync resources loaded
firstPaint / firstContentfulPaintfirst pixels painted
firstMeaningfulPaint / firstMeaningfulPaintCandidatemain content painted
networkAlmostIdle≤2 network connections for a period
networkIdleno network activity for a period
callbackthe page explicitly signals readiness (see below)

A web font is the most common reason to need one of the later events: load does not wait for @font-face files, so a page can print in the fallback font before its own arrives. See Assets.

The callback event — signal readiness yourself

For fully custom "ready" logic (e.g. after a chart library finishes drawing), use callback. The renderer then waits until the page calls back to the internal server, rather than watching browser events.

{ "pdf": { "waitLifecycleEvent": "callback" } }

In the page, fetch the callback URL when your content is ready:

<script>
// ...draw charts / load data...
window.addEventListener('load', () => {
fetch(window.location.pathname + '/callback');
});
</script>

The renderer prints the PDF as soon as that request arrives (bounded by the render timeout). See the callback example in the Gallery.

The endpoint accepts GET and POST, and ignores the request body — the arrival of the request is the whole signal. POST is there for navigator.sendBeacon, which can only send that verb and is the one way to signal from a page that is being torn down; fetch and XMLHttpRequest send a GET and are what the snippet above uses.

The callback endpoint lives on the loopback-only internal server the browser is already talking to — the page reaches it via its own window.location. See Internal callback server.