Appearance
Templating (Safe Placeholder Subset)
Templates support placeholders inside string values using a strict safe grammar.
Allowed Syntax
Only these forms are supported:
- Variable path (dot notation):
json
{ "type": "text", "value": "Hello {{ customer.name }}" }- Allowed filter chain:
json
{ "type": "text", "value": "{{ issued_at|date('%d/%m/%Y') }}" }Supported filters:
date(format?)datetime(format?)currency(symbol?, decimals?)concat(value?)
Table Loop Alias (item)
Dynamic table columns run in a per-row context with item:
json
{
"type": "table",
"source": "lines",
"columns": [
{ "label": "Concept", "value": "{{ item.concept }}" },
{ "label": "Qty", "value": "{{ item.quantity }}" }
]
}When source resolves to an empty array, renderer inserts one row using empty_message.
Static table modes are also supported:
tablewithdata: no loop, cells are rendered directly (placeholders still resolve).tablewithcolumnsand nosource: renderer creates a single row fromcolumns[].value.
Examples
json
{
"elements": [
{ "type": "text", "value": "{{ total|currency('€', 2) }}" },
{ "type": "text", "value": "{{ prefix|concat(suffix) }}" },
{ "type": "text", "value": "{{ created_at|datetime('%Y-%m-%d %H:%M') }}" }
]
}Not Allowed
These are rejected with actionable issues (for example PLACEHOLDER_UNSAFE_EXPRESSION):
- function calls in expressions
- control flow tags (
{% ... %}) - comments (
{# ... #}) - arbitrary expression syntax outside the safe placeholder subset
Example rejected payload:
json
{ "type": "text", "value": "{{ cycler.__init__.__globals__.os.popen('id').read() }}" }Missing Data Behavior
POST /api/public/validate
- Missing placeholders become
PLACEHOLDER_MISSINGwarnings.
POST /api/public/render
- Missing placeholders are replaced with empty string.
Data Payload
When rendering with data:
json
{
"template": { "elements": [ { "type": "text", "value": "{{ total }}" } ] },
"data": { "total": 123.45 }
}