Skip to main content

HTML Templates

HTML is the most flexible format in Velkin. Templates render through headless Chromium, so anything a modern browser supports works here — flexbox, grid, SVG, web fonts, and CSS print rules.

Asset extension.html, .htm
EngineHandlebars.js 4.7.8 (goja VM)
RendererChromium (chromedp) → PDF
Endpointsrender-pdf, preview, preview-html

Data binding

The data you POST (or the report's sampleData when you omit it) is the Handlebars context:

<h1>Invoice #{{number}}</h1>
<p>Bill to: {{customer.name}}</p>

{{#each items}}
<tr><td>{{description}}</td><td>{{amount}} </td></tr>
{{/each}}

{{#if overdue}}<span class="badge">Overdue</span>{{/if}}

By default Handlebars HTML-escapes values. Use triple-stache {{{rawHtml}}} only for values you trust — template authors are trusted, but the data may not be.

Reusable logic with helpers.hbs

The report's helpers.hbs is JavaScript, executed before each template compiles. Register helpers and partials with the standard Handlebars.js API:

helpers.hbs
Handlebars.registerHelper('money', (n) =>
new Intl.NumberFormat('it-IT', { style: 'currency', currency: 'EUR' }).format(n),
);
Handlebars.registerPartial(
'row',
'<tr><td>{{description}}</td><td>{{money amount}}</td></tr>',
);
template.html
<tbody>{{#each items}}{{> row}}{{/each}}</tbody>

Inline partials ({{#*inline}}) work too, but they belong inside the template, not in helpers.hbs (which is JavaScript). See Helpers & Partials for the full picture, including the goja sandbox and its limits.

Page size & print options

Page geometry comes from options.json (outputOptions.pdf), not from the template. The renderer passes these to Chromium's print engine:

OptionEffect
formatPaper size: A0A6, B4, B5, LETTER, LEGAL, TABLOID, LEDGER.
landscapeRotate to landscape.
printBackgroundInclude background colors and images (off → backgrounds drop out).
preferCSSPageSizeWhen true, a CSS @page { size: … } rule wins over format.

Because rendering is real Chromium print, standard print CSS applies:

/* Force a page break before each section */
.section { break-before: page; }

/* Avoid breaking a row across pages */
tr { break-inside: avoid; }

/* Table headers repeat on every page automatically */
thead { display: table-header-group; }

/* Page size via CSS (needs preferCSSPageSize: true) */
@page { size: A4; margin: 18mm; }
Margins and headers/footers

Velkin does not set print margins or inject header/footer templates — it only passes paper size, orientation, background, and preferCSSPageSize to Chromium. To control margins, use CSS @page { margin } (with preferCSSPageSize: true) or simply pad the <body>. Repeating headers/footers rely on CSS print techniques (e.g. position: fixed elements repeat on each page, thead repeats in tables) — there is no separate header/footer option.

Embedded and remote assets

Images, fonts, and stylesheets are fetched by Chromium while the page loads. Two ways to include them:

  • Inline as data URIs (data:image/png;base64,…) — always allowed, fully self-contained, and immune to network policy. Best for logos and small assets.
  • Remote http(s) URLs — fetched through Velkin's SSRF filter. Public hosts are allowed; private/loopback/link-local addresses are blocked by default (see Security model). file:, chrome: and similar local schemes are always blocked.

JavaScript is off by default

The PDF is captured after the DOM is ready, so to keep untrusted templates from mutating the page or exfiltrating data, JavaScript execution is disabled by default. Static HTML/CSS renders fine without it.

If your templates need a client-side chart library (Chart.js, D3, …) and you trust their source, an operator can enable scripts with PDF_ALLOW_JAVASCRIPT=true. See Installation.