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: A0–A6, 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.