$tpl — Template Directive
Renders a Liquid template (via the Fluid library) with the full evaluation context injected as template model. Produces structured HTML, Markdown, or plain text output for multi-line documents.
| Property | Value |
|---|---|
| Directive name | tpl |
| Required isolation level | Safe |
| Project | Template.Services |
| Engine | Fluid (Liquid-compatible) |
| Syntax form | Template literal: $tpl`...liquid...` |
| Pipeline | Separate from expression interpolation — Liquid tags {{ }} are Liquid output, not BizFirst wildcards |
$tpl script, {{ }} is Liquid syntax, not BizFirst DoubleBrace wildcards. The Fluid engine resolves these. BizFirst wildcards ({@ } or {{ }}) in the surrounding node field are evaluated first, and their string results become part of the template model passed to Fluid.
Template Model Variables
The following variables are available inside every Liquid template:
| Liquid variable | Equivalent directive |
|---|---|
{{ tenant.name }} | $ctx.tenant.name |
{{ tenant.currency }} | $ctx.tenant.currency |
{{ user.firstName }}, {{ user.email }} | $ctx.user.* |
{{ now }} | $ctx.now |
{{ today }} | $ctx.today |
{{ input.current }} | $input.current |
{{ input.items }} | $input.items |
{{ vars.myVar }} | $var.myVar |
{{ exec.executionId }} | $exec.executionId |
{{ flow.current.name }} | $flow.current.name |
Complex Scenarios
Scenario 1 — HTML Payslip Email Body
Generate a full HTML email body for a payslip, iterating over earnings lines:
{@ $tpl`
<h2>Payslip — {{ input.current.employeeName }}</h2>
<p>Period: {{ input.current.periodStart }} to {{ input.current.periodEnd }}</p>
<table>
<tr><th>Description</th><th>Amount</th></tr>
{% for line in input.current.earnings %}
<tr>
<td>{{ line.description }}</td>
<td>{{ line.amount | round: 2 }} {{ tenant.currency }}</td>
</tr>
{% endfor %}
<tr><th>Net Pay</th><th>{{ input.current.netPay | round: 2 }}</th></tr>
</table>
<p>Generated: {{ now }}</p>
` }
Scenario 2 — Conditional Multi-Section Report
A management summary email that includes an "overdue" warning section only when applicable:
{@ $tpl`
Dear {{ user.firstName }},
Your {{ flow.current.name }} run for {{ tenant.name }} has completed.
{% if vars.overdueCount > 0 %}
⚠ WARNING: {{ vars.overdueCount }} invoices are overdue.
Total overdue amount: {{ vars.overdueTotal }} {{ tenant.currency }}
{% endif %}
Summary:
- Processed: {{ vars.processedCount }} invoices
- Failed: {{ vars.failedCount }} invoices
- Total value: {{ vars.totalValue }} {{ tenant.currency }}
Execution ID: {{ exec.executionId }}
` }
Scenario 3 — Markdown Report for Slack/Teams
Generate Markdown-formatted output for posting to a messaging platform:
{@ $tpl`
*Payroll Run Complete* — {{ tenant.name }}
| Metric | Value |
|--------|-------|
| Employees | {{ input.current.employeeCount }} |
| Total Gross | {{ input.current.totalGross | round: 2 }} {{ tenant.currency }} |
| Total Tax | {{ input.current.totalTax | round: 2 }} {{ tenant.currency }} |
| Net Payroll | {{ input.current.netPayroll | round: 2 }} {{ tenant.currency }} |
{% if input.current.errors.size > 0 %}
*Errors ({{ input.current.errors.size }}):*
{% for err in input.current.errors %}
• {{ err.employeeName }}: {{ err.message }}
{% endfor %}
{% endif %}
` }
Scenario 4 — Template with Pre-Resolved Expression Value
Mix BizFirst expression results with Liquid — the BizFirst expression is evaluated first, injected as a variable, then Liquid renders:
// Node field: first evaluate BizFirst expressions, then render Liquid
// Step 1: {@ $js`return calculateBonus(context.input.current)` } → "1250.00"
// Step 2: that value is available as vars.bonusAmount in $tpl
{@ $tpl`
Dear {{ input.current.firstName }},
We are pleased to advise that your performance bonus for Q4 has been approved.
Bonus amount: {{ vars.bonusAmount }} {{ tenant.currency }}
This will be included in your {{ vars.nextPayPeriod }} payslip.
Kind regards,
{{ tenant.hrManagerEmail }}
` }
Scenario 5 — Dynamic Table from Variable Array
Render a data table from a JSON array stored in a variable — useful for embedded reports:
{@ $tpl`
Reconciliation Report — {{ today }}
{% assign rows = vars.reconRows %}
{% if rows.size == 0 %}
No discrepancies found. All records reconcile correctly.
{% else %}
DISCREPANCIES FOUND ({{ rows.size }}):
{% for row in rows %}
Employee : {{ row.employeeId }} — {{ row.name }}
Expected : {{ row.expected | round: 2 }}
Actual : {{ row.actual | round: 2 }}
Delta : {{ row.delta | round: 2 }}
--------
{% endfor %}
{% endif %}
` }
Scenario 6 — Legal Document Template with Locale Date
Generate a legally formatted notice letter with correct date presentation per tenant locale:
{@ $tpl`
OFFICIAL NOTICE
Date: {{ now | date: "%d %B %Y" }}
To: {{ input.current.recipientName }}
{{ input.current.address.line1 }}
{{ input.current.address.city }}, {{ input.current.address.country }}
Re: {{ vars.noticeSubject }}
Dear {{ input.current.salutation }} {{ input.current.lastName }},
{{ vars.noticeBody }}
This notice is issued by {{ tenant.name }} under reference {{ exec.correlationId }}.
Authorised by: {{ user.name }}
Title: {{ user.department }} Manager
` }
Common Errors
| Error | Cause | Fix |
|---|---|---|
LiquidParseError: unexpected tag | Liquid syntax error in template body | Validate Liquid syntax; check mismatched {% if %}/{% endif %} |
| Liquid output empty for a variable | Variable name typo or path doesn't exist in model | Check spelling; use {% if vars.x %}...{% endif %} to guard |
| BizFirst wildcards appear in output | Used {@ } inside Liquid block — not evaluated by Liquid | Pre-resolve into a $var first, then reference {{ vars.name }} in Liquid |
FilterNotFound: myFilter | Using a custom Liquid filter not registered | Register custom Fluid filters in TemplateOptions during DI setup |