Templates Module
The templates module provides powerful template rendering capabilities using Liquid templating syntax, with support for dynamic data processing through Lua functions. It enables creation of dynamic documents, emails, reports, and any text-based content with sophisticated templating features.
Functions
template.render(id_or_handle, data)
Renders a pre-configured template by its ID or handle, with optional function preprocessing.
Parameters:
id_or_handle(string): Template UUID or handle identifierdata(table): Data context for template rendering
Returns:
string: Rendered template outputfalse: On error (template not found, rendering failure, or permission denied)
Basic Examples:
-- Simple template rendering
local output = template.render("welcome-email", {
name = "John Doe",
company = "Acme Corp",
signup_date = "2024-01-15"
})
if output then
print("Email content generated")
notifiers.recipient(user_id, "Welcome!", output)
else
print("Failed to render template")
end
-- Complex data structure
local report_data = {
report_date = "2024-01-15",
metrics = {
total_orders = 150,
revenue = 12500.00,
average_order = 83.33
},
top_products = {
{name = "Widget A", sales = 45},
{name = "Widget B", sales = 38},
{name = "Widget C", sales = 29}
}
}
local report = template.render("daily-report", report_data)template.render_raw(template_string, data)
Renders a raw Liquid template string without requiring a pre-configured template.
Parameters:
template_string(string): Liquid template markupdata(table): Data context for template rendering
Returns:
string: Rendered template outputfalse: On rendering error
Examples:
-- Simple variable substitution
local output = template.render_raw(
"Hello, {{ name }}! Welcome to {{ company }}.",
{name = "Jane Smith", company = "CoCoCo"}
)
-- Output: "Hello, Jane Smith! Welcome to CoCoCo."
-- Conditional rendering
local template_str = [[
{% if user.premium %}
Welcome back, {{ user.name }}! You have premium access.
{% else %}
Hello {{ user.name }}! Consider upgrading to premium.
{% endif %}
]]
local output = template.render_raw(template_str, {
user = {name = "John", premium = true}
})
-- Loops and filters
local template_str = [[
Order Summary:
{% for item in items %}
- {{ item.name | upcase }}: ${{ item.price | round: 2 }}
{% endfor %}
Total: ${{ total | round: 2 }}
]]
local output = template.render_raw(template_str, {
items = {
{name = "Print Job A", price = 25.556},
{name = "Print Job B", price = 30.234}
},
total = 55.79
})Liquid Template Features
Variables and Filters
local template_str = [[
{{ name | upcase }}
{{ price | round: 2 }}
{{ description | truncate: 50 }}
{{ date | date: "%Y-%m-%d" }}
{{ text | strip_html | escape }}
{{ number | plus: 10 | times: 2 }}
]]
local output = template.render_raw(template_str, {
name = "John Doe",
price = 99.996,
description = "This is a very long description that will be truncated",
date = "2024-01-15T10:30:00Z",
text = "<p>HTML content</p>",
number = 5
})Control Flow
-- If/Else statements
local template_str = [[
{% if order.status == "pending" %}
Your order is being processed.
{% elsif order.status == "shipped" %}
Your order has been shipped!
{% else %}
Order status: {{ order.status }}
{% endif %}
]]
-- Case statements
local template_str = [[
{% case product.type %}
{% when "book" %}
This is a printed book.
{% when "poster" %}
This is a poster print.
{% when "card" %}
This is a greeting card.
{% else %}
Unknown product type.
{% endcase %}
]]
-- Unless (negative if)
local template_str = [[
{% unless user.verified %}
Please verify your email address.
{% endunless %}
]]Loops and Iterations
-- Basic for loop
local template_str = [[
{% for product in products %}
{{ forloop.index }}. {{ product.name }} - ${{ product.price }}
{% endfor %}
]]
-- Loop with limit and offset
local template_str = [[
{% for item in items limit:5 offset:10 %}
{{ item.name }}
{% endfor %}
]]
-- Loop variables
local template_str = [[
{% for item in items %}
{% if forloop.first %}First item: {% endif %}
{{ item.name }}
{% if forloop.last %}(Last item){% endif %}
{% unless forloop.last %},{% endunless %}
{% endfor %}
]]
-- TableRow for HTML tables
local template_str = [[
<table>
{% tablerow product in products cols:3 %}
{{ product.name }}
{% endtablerow %}
</table>
]]Function-Enhanced Templates
Templates can be bound to Lua functions for dynamic data preprocessing. When a template has an associated function:
- The function's
main(args)is called with the provided data - The function's return value becomes the template context
- The template is rendered with the processed data
-- Template function (configured separately in the system):
function main(args)
-- Process input arguments
local order_id = args.order_id
-- Fetch additional data
local success, response = graphql.query([[
query($id: ID!) {
getOrder(id: $id) {
id
customer { name, email }
items { name, quantity, price }
total
status
}
}
]], { id = order_id })
if not success then
return { error = "Failed to fetch order" }
end
local order = response.data.getOrder
-- Calculate additional fields
local item_count = 0
for _, item in ipairs(order.items) do
item_count = item_count + item.quantity
end
-- Return enriched data for template
return {
order = order,
item_count = item_count,
formatted_total = string.format("$%.2f", order.total),
status_message = get_status_message(order.status),
generated_at = os.date("%Y-%m-%d %H:%M:%S")
}
end
-- Template content:
-- "Order #{{ order.id }} contains {{ item_count }} items. Total: {{ formatted_total }}"
-- Usage in Lua:
local output = template.render("order-summary", { order_id = "12345" })Advanced Template Patterns
Email Templates
function send_order_confirmation(order_id, customer_email)
-- Template with HTML and plain text versions
local html_template = [[
<!DOCTYPE html>
<html>
<head>
<style>
.header { background: #007bff; color: white; padding: 20px; }
.item { border-bottom: 1px solid #eee; padding: 10px; }
.total { font-weight: bold; font-size: 1.2em; }
</style>
</head>
<body>
<div class="header">
<h1>Order Confirmation</h1>
</div>
<p>Dear {{ customer.name }},</p>
<p>Thank you for your order #{{ order.id }}!</p>
<h2>Order Details:</h2>
{% for item in order.items %}
<div class="item">
{{ item.name }} x {{ item.quantity }} @ ${{ item.price | round: 2 }}
</div>
{% endfor %}
<div class="total">
Total: ${{ order.total | round: 2 }}
</div>
</body>
</html>
]]
local plain_template = [[
Order Confirmation
Dear {{ customer.name }},
Thank you for your order #{{ order.id }}!
Order Details:
{% for item in order.items %}
- {{ item.name }} x {{ item.quantity }} @ ${{ item.price | round: 2 }}
{% endfor %}
Total: ${{ order.total | round: 2 }}
]]
-- Fetch order data
local order_data = fetch_order_data(order_id)
-- Render both versions
local html_output = template.render_raw(html_template, order_data)
local plain_output = template.render_raw(plain_template, order_data)
-- Send multipart email
return send_multipart_email(customer_email, "Order Confirmation", {
html = html_output,
plain = plain_output
})
endReport Generation
function generate_monthly_report(month, year)
local report_template = [[
# Monthly Report - {{ month }}/{{ year }}
## Executive Summary
Total Revenue: ${{ summary.revenue | round: 2 }}
Total Orders: {{ summary.order_count }}
Average Order Value: ${{ summary.avg_order_value | round: 2 }}
## Top Products
{% for product in top_products %}
{{ forloop.index }}. **{{ product.name }}**
- Units Sold: {{ product.units }}
- Revenue: ${{ product.revenue | round: 2 }}
{% endfor %}
## Customer Metrics
New Customers: {{ metrics.new_customers }}
Returning Customers: {{ metrics.returning_customers }}
Customer Retention Rate: {{ metrics.retention_rate | times: 100 | round: 1 }}%
## Trends
{% if trends.revenue_change > 0 %}
📈 Revenue increased by {{ trends.revenue_change | round: 1 }}%
{% else %}
📉 Revenue decreased by {{ trends.revenue_change | abs | round: 1 }}%
{% endif %}
---
Generated on {{ generated_date }}
]]
-- Collect report data
local report_data = collect_monthly_metrics(month, year)
report_data.month = month
report_data.year = year
report_data.generated_date = os.date("%Y-%m-%d %H:%M:%S")
return template.render_raw(report_template, report_data)
endDynamic Form Letters
function generate_invoice(invoice_data)
local invoice_template = [[
{% assign subtotal = 0 %}
INVOICE
=======
Invoice #: {{ invoice.number }}
Date: {{ invoice.date | date: "%B %d, %Y" }}
Due Date: {{ invoice.due_date | date: "%B %d, %Y" }}
Bill To:
{{ customer.name }}
{{ customer.address.street }}
{{ customer.address.city }}, {{ customer.address.state }} {{ customer.address.zip }}
Items:
------
{% for item in invoice.items %}
{% assign line_total = item.quantity | times: item.unit_price %}
{% assign subtotal = subtotal | plus: line_total %}
{{ item.description }}
Qty: {{ item.quantity }} × ${{ item.unit_price | round: 2 }} = ${{ line_total | round: 2 }}
{% endfor %}
Subtotal: ${{ subtotal | round: 2 }}
{% if invoice.discount > 0 %}
Discount ({{ invoice.discount }}%): -${{ subtotal | times: invoice.discount | divided_by: 100 | round: 2 }}
{% assign subtotal = subtotal | times: 100 | minus: invoice.discount | divided_by: 100 %}
{% endif %}
Tax ({{ invoice.tax_rate }}%): ${{ subtotal | times: invoice.tax_rate | divided_by: 100 | round: 2 }}
{% assign total = subtotal | times: 100 | plus: invoice.tax_rate | divided_by: 100 %}
TOTAL DUE: ${{ total | round: 2 }}
Payment Terms: {{ invoice.payment_terms }}
]]
return template.render_raw(invoice_template, invoice_data)
endBest Practices
Validate template data: Ensure all required fields exist
luafunction safe_template_render(template_id, data) -- Validate required fields local required_fields = {"name", "email", "order_id"} for _, field in ipairs(required_fields) do if not data[field] then print("Missing required field: " .. field) return false end end return template.render(template_id, data) endHandle missing variables gracefully: Use default filters
lualocal template_str = [[ Hello {{ name | default: "Guest" }}! Your balance is ${{ balance | default: 0 | round: 2 }} ]]Escape user content: Prevent XSS in HTML templates
lualocal template_str = [[ <p>Comment: {{ user_comment | escape }}</p> <div>{{ user_html | strip_html }}</div> ]]Cache compiled templates: Improve performance
luafunction cached_template_render(template_id, data) local cache_key = "template:compiled:" .. template_id local compiled = cache.get(cache_key) if not compiled then -- Fetch and compile template compiled = fetch_and_compile_template(template_id) cache.put_ttl(cache_key, compiled, 3600) end return render_compiled_template(compiled, data) end
Security and Performance
Security:
- Requires actor permissions to access templates
- Function execution runs in sandboxed Lua environment
- User input is automatically escaped in HTML context
- Template size limits prevent memory exhaustion
Performance:
- Templates are compiled once and cached
- Large datasets should be paginated
- Complex calculations should be done in preprocessing functions
- Avoid deeply nested loops in templates