Portal Community
PropertyValue
Directive nametpl
Required isolation levelSafe
ProjectTemplate.Services
EngineFluid (Liquid-compatible)
Syntax formTemplate literal: $tpl`...liquid...`
PipelineSeparate from expression interpolation — Liquid tags {{ }} are Liquid output, not BizFirst wildcards
Two separate systems Inside a $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 variableEquivalent 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

ErrorCauseFix
LiquidParseError: unexpected tagLiquid syntax error in template bodyValidate Liquid syntax; check mismatched {% if %}/{% endif %}
Liquid output empty for a variableVariable name typo or path doesn't exist in modelCheck spelling; use {% if vars.x %}...{% endif %} to guard
BizFirst wildcards appear in outputUsed {@ } inside Liquid block — not evaluated by LiquidPre-resolve into a $var first, then reference {{ vars.name }} in Liquid
FilterNotFound: myFilterUsing a custom Liquid filter not registeredRegister custom Fluid filters in TemplateOptions during DI setup