CodamAIDocs
Topicdone

The template language

FreeMarker in a sandbox: how a template reads the payload, how numbers and dates are formatted, how assets and SVG graphics get in, and what is locked.

Variants
DOCUMENT (XHTML)WORD (Flat OPC)TABLE (CRMS table document)asset:<name>crms.svg

What this is about

The source of a template is FreeMarker. FreeMarker is a template language for Java: plain text with placeholders like ${title} and directives like <#if> and <#list>. CRMS runs it in a sandbox. That is a sealed-off environment in which a template only sees the data you give it and reaches nothing else.

What the template writes

What a template writes depends on its output type. The automatic escaping follows from it too.

Output typeThe template writesEscapingFormats
DOCUMENTXHTML, that is HTML as clean XMLXHTMLHTML, PDF
WORDthe whole Word package as one XML document (Flat OPC)XMLDOCX
TABLEthe CRMS table document (<workbook>, <sheet>, <row>, <cell>)XMLXLSX, CSV, ODS

Escaping means: characters like < and & in a value are rewritten so they appear as text and do not become markup. If the payload holds <script>alert(1)</script>, <p>${title}</p> turns it into <p>&lt;script&gt;alert(1)&lt;/script&gt;</p>. The details of each format are in Output types and formats.

Reading the payload

The payload is the top level of the template. Its fields are right there.

<#-- Payload: { "title": "Audit hall 3", "customer": { "name": "ACME" },
                "items": [ { "label": "HACCP", "amount": 480 } ] } -->
<h1>${title}</h1>
<p>Customer: ${customer.name}</p>
<table>
  <#list items as item>
    <tr><td>${item.label}</td><td>${item.amount}</td></tr>
  </#list>
</table>
<p>Inspector: ${(inspector)!"–"}</p>
<#if remarks?has_content><p>${remarks}</p></#if>
  • ${…} writes a value, escaped.
  • <#list> runs over a list, <#if> checks a condition.
  • ! sets a fallback value when a field is missing. ?? asks whether it is there.
  • <#assign>, <#macro> and <#function> build helpers within the same source. The hub templates use them a lot, for example for headings and the table of contents.

The payload has at most two levels: title, customer.name, items[].amount. Deeper is not possible. What CRMS checks about the payload is described in The payload check.

Numbers, dates, booleans

For every render, CRMS sets the language and the time zone of the document. FreeMarker formats accordingly:

ExpressionValueOutput for de
${amount}1234.51.234,5
${amount?string("0.00")}1234.51234,50
${due?string("dd.MM.yyyy")}"2026-10-17", declared as DATE17.10.2026
${done}truetrue

A field the revision declares as DATE or DATETIME becomes a real date, even when it arrives as ISO text. That is how JSON carries it. A DATE is midnight in the document’s time zone, never in the server’s. Without a declaration, an ISO text stays text. Where language and time zone come from is described in Language and time zone.

Assets

You name an asset of the revision as asset:<name>. What CRMS does with it depends on the format:

FormatWhere asset: standsWhat happens
HTMLsrc, href, CSS url()becomes a data: address, so the page is complete on its own
PDFimages, fonts (@font-face { src: url('asset:own.ttf') }), style sheetsloaded from memory. Every other address is refused
DOCX<pkg:binaryData>asset:logo.png</pkg:binaryData>filled with the content of the asset
<img src="asset:logo.png" alt="Logo"/>

If the template names an asset the revision does not have, rendering fails, for HTML with render-asset-unknown.

Pictures that belong to the request, such as a signature or the photos of an inspection, are not assets. The template names them attachment:<name>. More in Images and SVG.

SVG from the payload: crms.svg

A graphic the caller draws itself, for example a diagram, comes as SVG text in the payload. Escaping would only write it as text. That is what the function crms.svg is for:

<div class="figure">${crms.svg(diagramSvg)}</div>

CRMS checks the SVG against a list of allowed elements and attributes and then inserts it. crms belongs to CRMS: a payload field named crms does not replace the function.

Output typecrms.svg writes
DOCUMENTthe SVG directly into the XHTML. HTML shows it, PDF draws it as vector graphics
WORDa placeholder inside <w:p>. It becomes an embedded picture
TABLEnothing: 422 template-svg-not-for-output-type|table

What is allowed and what is refused is described in Images and SVG.

What is locked

Allowed and locked
Allowed
the language itself
  • ${…}, <#if>, <#list>, <#assign>
  • <#macro>, <#function> in the same source
  • built-ins such as ?string, ?has_content, ?size, ?filter
  • fallback values with !, checks with ??
  • crms.svg(…)
Locked
everything that leads out of the template
  • ?new: no class can be created
  • ?api: no method behind a value
  • <#include>, <#import>: there is no other file and no other template
  • method calls on objects: the payload holds only text, numbers, booleans, dates, lists and maps

On top of that, rendering has limits: the output has a maximum size, and a template that does not end is cancelled after the time limit. See Execution and limits.

Errors in the template

Errors come as keys with line and column in the source. The client does not get FreeMarker’s text.

KeyWhen
template-syntax-error|<line>|<column>the source cannot be parsed
template-render-failed|<line>|<column>something went wrong while rendering, for example a missing value or a locked ?new
render-output-too-largethe document got larger than allowed

CRMS already finds a syntax error on submit. The renderer parses the draft, and a revision that cannot be parsed is never created: 422 template-source-rejected with violations: [{"field": "source", "rule": "template-syntax-error|1|7"}].

Pitfalls

Next

Search