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:
- spins up a fresh VM and loads Handlebars.js into it,
- executes
helpers.hbsas JavaScript, so anyHandlebars.registerHelper/Handlebars.registerPartialcalls take effect, - 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:
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:
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.hbsand call them with{{> name}}from any template:helpers.hbsHandlebars.registerPartial('lineItem','<tr><td>{{description}}</td><td>{{moneyEUR amount}}</td></tr>',); -
Inline partials — define them inside the template with
{{#*inline}}, then call them in the same template:
{{#*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 ECMAScriptIntl; 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 leavehelpers.hbsempty. - Need formatting, math, date handling, or conditional logic the built-ins
can't express? Write JavaScript helpers in
helpers.hbs.