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.

VariableTypeDescription
.vars["applicantName"]stringFull name of the applicant
.vars["nationalId"]stringNational ID number
.vars["referenceNumber"]stringApplication reference number
.vars["applicationDate"]dateDate the application was submitted
.vars["expiryDate"]dateCertificate 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"]stringName of the issuing organisation
.vars["authorisedBy"]stringName or title of the authorising officer
.vars["logoUrl"]stringURL of the organisation logo asset
.vars["serviceDescription"]stringOnly 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 mm units for widths and margins to match A4/Letter paper (e.g. width: 210mm; padding: 20mm 25mm)
  • Wrap the page content in a .page div 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"]!''} &nbsp;|&nbsp; 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>

On this page