Skip to main content

Helpers & Partials

Every report has a helpers.hbs file holding the reusable logic shared by its templates. There is one rendering engine: Handlebars.js 4.7.8 running in a goja JavaScript VM. On every render Velkin:

  1. spins up a fresh VM and loads Handlebars.js into it,
  2. executes helpers.hbs as JavaScript, so any Handlebars.registerHelper / Handlebars.registerPartial calls take effect,
  3. compiles the active template and renders it against your data.

So helpers.hbs is always JavaScript — there is no separate "partials mode" and nothing auto-detects its contents. An empty file is fine; the template still renders with Handlebars' built-in helpers (if, unless, each, with, lookup). The same helpers.hbs applies to HTML, DOCX, and XLSX templates alike.

Registering helpers

Register helpers with the standard Handlebars.js API:

helpers.hbs
Handlebars.registerHelper('moneyEUR', function (amount) {
return new Intl.NumberFormat('it-IT', {
style: 'currency',
currency: 'EUR',
}).format(amount);
});

Handlebars.registerHelper('upper', function (s) {
return String(s).toUpperCase();
});

Invoke them like any Handlebars helper from your templates:

template.html
<p>Total: {{moneyEUR invoice.total}}</p>
<h1>{{upper customer.name}}</h1>

Because the file runs against the full Handlebars.js runtime, the rest of its API is available too: block helpers via options.fn(this) / options.inverse(this), Handlebars.SafeString to emit un-escaped markup, and Handlebars.registerPartial.

Partials

Two ways, both stock Handlebars.js:

  • Registered partials — declare them in helpers.hbs and call them with {{> name}} from any template:

    helpers.hbs
    Handlebars.registerPartial(
    'lineItem',
    '<tr><td>{{description}}</td><td>{{moneyEUR amount}}</td></tr>',
    );
    template.html
    <tbody>{{#each items}}{{> lineItem}}{{/each}}</tbody>
  • Inline partials — define them inside the template with {{#*inline}}, then call them in the same template:

    template.html
    {{#*inline "lineItem"}}<tr><td>{{description}}</td></tr>{{/inline}}
    <tbody>{{#each items}}{{> lineItem}}{{/each}}</tbody>
warning
{{#*inline}} goes in the template, not helpers.hbs

{{#*inline}} is Handlebars syntax, not JavaScript. It belongs in a .html/DOCX/XLSX template. If you paste it into helpers.hbs the render fails with 422 template_error, because that file is executed as JavaScript.

What runs in the sandbox

Helpers execute in a goja VM (an ES2020 JavaScript interpreter written in Go) — not Node and not a browser. This is a deliberate security boundary (see the Security model):

  • A fresh VM is created for every render, so concurrent requests never share mutable state.
  • There is no require, fs, fetch, process, network access, or timers (setTimeout/setInterval). Helpers are pure data-to-string functions.
  • Standard ECMAScript built-ins work normally — String, Array (map, filter, …), Math, Date, JSON, Object, regular expressions.
  • Two globals are shimmed in for convenience:
    • console.log / warn / error / debug — present but no-ops (output is discarded), so debugging calls don't crash the render.
    • Intl.NumberFormat — a minimal shim that formats Italian Euro values (e.g. 1.234,56 €). It is not the full ECMAScript Intl; the locale and options arguments are ignored, so other currencies/locales won't format correctly. For those, write the formatting by hand.

If helpers.hbs or the template throws while executing, the render is rejected with 422 template_error and the JavaScript error message. There is no execution timeout on the VM, though — a runaway loop in a helper blocks that request. That's acceptable under Velkin's trusted-author model, but keep it in mind.

Data is plain JSON

The render data is serialized to JSON and re-parsed inside the VM before your helpers see it. They always receive plain JavaScript objects, arrays, strings, and numbers — never Go-specific types — so obj.field, arr.map(...), and typeof behave as you'd expect.

Escaping

On the HTML pipeline, Handlebars' default HTML-escaping is on{{expr}} escapes, {{{expr}}} and Handlebars.SafeString do not. On the DOCX/XLSX pipelines escaping is off, because the OOXML parts are already entity-encoded and a second pass would corrupt the markup.

jsreport compatibility

If you're bringing a helpers file over from jsreport, Velkin strips the jsreport-specific preamble for you — lines that require('jsreport-proxy') or call jsreport.assets.require(...) are removed before the script runs, so most jsreport helper files work unchanged.

Which approach should I use?

  • No JavaScript needed? Use the template's built-in helpers ({{#each}}, {{#if}}) and inline partials, and leave helpers.hbs empty.
  • Need formatting, math, date handling, or conditional logic the built-ins can't express? Write JavaScript helpers in helpers.hbs.