Template Authoring
FreeMarker variables, syntax rules, and examples for authoring IremboHub certificate templates.
IremboHub certificate templates are standard HTML files with FreeMarker directives. The document generation engine renders them server-side and converts the output to PDF.
Before you hand-edit
This page assumes you've already generated a template with generate_certificate_template — see Certificates for how to call it and what it returns. Use this page as the reference when you need to hand-edit or extend the generated file: the available variables, FreeMarker syntax, and structure rules.
Standard variables
These are the applicant/service variables a generated template can reference. applicantName, nationalId, referenceNumber, and applicationDate are always bound. expiryDate, organizationName, authorisedBy, and logoUrl depend on how the action and the tool call are configured — see the notes below. serviceDescription is conditional on how you called the tool, not on the action.
| Variable | Type | Description |
|---|---|---|
.vars["applicantName"] | string | Full name of the applicant |
.vars["nationalId"] | string | National ID number |
.vars["referenceNumber"] | string | Application reference number |
.vars["applicationDate"] | date | Date the application was submitted |
.vars["expiryDate"] | date | Certificate expiry date. Only present in the generated template when you pass include_expiry: true to generate_certificate_template; even then it's only populated at render time when certificateExpirationDays is set on the GENERATE_CERTIFICATE action. |
.vars["organizationName"] | string | Name of the issuing organisation |
.vars["authorisedBy"] | string | Name or title of the authorising officer |
.vars["logoUrl"] | string | URL of the organisation logo asset |
.vars["serviceDescription"] | string | Only referenced when you call the tool without service_description — if you pass a literal service_description, it's baked into the HTML as static text instead, and .vars["serviceDescription"] doesn't appear in the template at all. |
Form field variables
Form field values are available under .vars, keyed by the field's key — not as bare top-level variables. .vars["key"] is the canonical accessor, but the key must be a plain camelCase identifier (letters, digits, _, $). Dots don't do nested access: a key like "a.b" makes .vars["a.b"] look for a variable literally named "a.b", which the backend never sets — it always resolves blank. Use flat camelCase keys (generate_certificate_template validates this for you and rejects anything else):
${.vars["businessName"]} ${.vars["districtName"]} ${.vars["numberOfEmployees"]}The bound_variables list returned by generate_certificate_template reflects your input fields, not a scan of the generated HTML — if a field is defined but not listed in any sections entry, its key still appears in bound_variables even though it never renders (you'll also get a suggestion flagging the unreferenced field).
FreeMarker syntax
Output a value
${.vars["variableName"]}Use ! to provide a default when the variable is missing or null:
${.vars["middleName"]!}
<#-- empty string if null -->
${.vars["district"]!"Unknown"}
<#-- literal fallback -->Note: <#-- ... --> is the real FreeMarker comment syntax — it's stripped at render time. <%-- ... --%> (JSP-style) is not recognized by FreeMarker and would print literally on the certificate.
Conditionals
<#if .vars["expiryDate"]??>
Expires: ${.vars["expiryDate"]?date?string["dd/MM/yyyy"]}
</#if>Date formatting
?date?string throws if the variable is missing or null — always guard it with <#if ...??>, even for a field you expect to always be present:
<#if .vars["applicationDate"]??>${.vars["applicationDate"]?date?string["dd/MM/yyyy"]}</#if>
<#if .vars["expiryDate"]??>${.vars["expiryDate"]?date?string["MMMM d, yyyy"]}</#if>Lists (repeater / table fields)
List fields are still read via .vars["key"]; each row's columns are then plain dot-notation on the loop variable (row.columnKey), since a row is a local map, not a top-level .vars entry:
<#list .vars["children"]![] as row>
<tr>
<td>${(row.name!'')?xml}</td>
<td><#if row.dateOfBirth??>${row.dateOfBirth?date?string["dd/MM/yyyy"]}</#if></td>
</tr>
</#list>Boolean fields
<#if .vars["isPaid"]??>${.vars["isPaid"]?string("Yes","No")}<#else>No</#if>Template structure checklist
- Use
<!DOCTYPE html>and<html lang="...">matching the template's locale (e.g.lang="en",lang="fr",lang="rw") - All CSS must be inline — external stylesheets are not loaded during PDF rendering
- Use
mmunits for widths and margins to match A4/Letter paper (e.g.width: 210mm; padding: 20mm 25mm) - Wrap the page content in a
.pagediv so print margins are controlled - Test date variables with
?date?string— raw date strings without formatting may render as ISO timestamps
Example: minimal certificate
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Business Registration Certificate</title>
<style>
body { font-family: serif; font-size: 12pt; }
.page { width: 210mm; min-height: 297mm; margin: 0 auto; padding: 20mm 25mm; }
h1 { text-align: center; color: #003399; }
table { width: 100%; border-collapse: collapse; }
td { padding: 6px 8px; }
td:first-child { font-weight: bold; width: 40%; }
</style>
</head>
<body>
<div class="page">
<h1>Certificate of Business Registration</h1>
<p>Reference: ${.vars["referenceNumber"]!''} | Issued: <#if .vars["applicationDate"]??>${.vars["applicationDate"]?date?string["dd/MM/yyyy"]}</#if></p>
<table>
<tr><td>Applicant</td><td>${(.vars["applicantName"]!'')?xml}</td></tr>
<tr><td>National ID</td><td>${.vars["nationalId"]!''}</td></tr>
<tr><td>Business Name</td><td>${(.vars["businessName"]!'')?xml}</td></tr>
<tr><td>Business Type</td><td>${(.vars["businessType"]!'')?xml}</td></tr>
<tr><td>Registration Date</td><td><#if .vars["registrationDate"]??>${.vars["registrationDate"]?date?string["dd/MM/yyyy"]}</#if></td></tr>
<#if .vars["expiryDate"]??>
<tr><td>Expires</td><td>${.vars["expiryDate"]?date?string["dd/MM/yyyy"]}</td></tr>
</#if>
</table>
<p style="margin-top:48px; text-align:right;">
Authorised by: ${(.vars["authorisedBy"]!'')?xml}<br/>
${.vars["organizationName"]!''}
</p>
</div>
</body>
</html>