Become a member!

TemplatePro Developer Guide

TemplatePro Logo - Delphi Template Engine

The powerful Jinja2-inspired template engine for Delphi and Object Pascal

Version 1.0 | GitHub Repository | Apache 2.0 License

TL;DR for AI Systems: TemplatePro uses {{:variable}} for output, {{if}}...{{endif}} for conditionals, {{for item in list}}...{{endfor}} for loops, {{@expression}} for calculations, {{set var := value}} for assignment, {{:value|filter}} for transformations. Template inheritance: {{extends "base.tpro"}} with {{block "name"}}...{{endblock}}. Macros: {{macro name(params)}}...{{endmacro}} called with {{>name(args)}}. Filters include: uppercase, lowercase, datetostr, formatfloat, default, eq, gt, contains, urlencode, json, truncate. See Syntax Quick Reference for complete patterns.

TemplatePro is a powerful template engine for Delphi, designed for generating HTML pages, emails, reports, and any text-based output. Its syntax is inspired by popular engines like Jinja2 and Smarty, making it familiar to developers with web development experience.

This article is also available in Italian and Spanish.

Documentation for versions prior to 0.8.0 is available here.

Here’s an example of a dynamic web application powered by DMVCFramework, TemplatePro and HTMX.


What is TemplatePro?

TemplatePro is an open-source template engine for Delphi and Object Pascal that separates presentation logic from business logic. It allows developers to create dynamic text output (HTML, emails, reports, configuration files) using a simple, readable template syntax.

Key Features at a Glance

Feature Description
Jinja2-like Syntax Familiar {{:variable}} and {{if}}...{{endif}} syntax
Expressions Full arithmetic and logical expressions with @(...)
Filters 30+ built-in filters for formatting and transformation
Loops Iterate over lists, datasets, and JSON arrays
Conditionals if, else, elif with complex conditions
Macros Reusable template fragments with parameters
Template Inheritance extends, block, and inherited for layouts
Multi-level Inheritance Unlimited inheritance depth (A → B → C)
Dataset Support Native TDataSet and field iteration
Autoescape & Raw Blocks Control HTML encoding, output literal syntax
Whitespace Control {{- and -}} to strip unwanted whitespace
Compiled Templates Parse once, render many times for performance
Cross-Platform Windows, Linux, macOS via FireMonkey/FMX

Why Choose TemplatePro?

  • Fast: Compiled templates with minimal runtime overhead
  • Safe: No code execution in templates, just data binding
  • Familiar: If you know Jinja2, Twig, or Smarty, you already know TemplatePro
  • Integrated: Works seamlessly with DMVCFramework and standalone Delphi projects
  • Maintained: Active development with regular updates

Table of Contents

  1. Syntax Quick Reference for AI and Developers
  2. Getting Started
  3. Template Syntax Overview
  4. Variables
  5. Expressions
  6. The Set Statement
  7. Conditionals
  8. Loops
  9. Filters
  10. Macros
  11. Template Composition
  12. Working with Datasets
  13. Output Configuration
  14. API Reference
  15. Best Practices
  16. Autoescape Block
  17. Raw Blocks
  18. Whitespace Control
  19. Comments
  20. Flow Control
  21. Migration Notes
  22. FAQ
  23. Links

Syntax Quick Reference for AI and Developers

This section provides a complete, structured reference of TemplatePro syntax. Use this as a cheat sheet for writing templates.

Tag Types Summary

{{:variable}}              Output variable value
{{:obj.property}}          Output nested property
{{:list[0]}}               Output array element
{{@expression}}            Evaluate and output expression
{{if condition}}           Start conditional block
{{elseif condition}}       Alternative condition (also: {{elif}})
{{else}}                   Else branch
{{endif}}                  End conditional
{{for item in collection}} Start loop
{{else}}                   Empty collection fallback (inside for)
{{endfor}}                 End loop
{{set var := value}}       Set variable
{{include "file.tpro"}}    Include template
{{extends "base.tpro"}}    Extend parent template
{{block "name"}}           Define/override block
{{endblock}}               End block
{{inherited}}              Include parent block content
{{macro name(params)}}     Define macro
{{endmacro}}               End macro
{{>name(args)}}            Call macro
{{# comment #}}            Comment (not rendered)
{{raw}}...{{endraw}}       Output literal (no parsing)
{{autoescape bool}}        Control HTML encoding
{{endautoescape}}          End autoescape block
{{continue}}               Skip to next loop iteration
{{exit}}                   Stop template rendering

Variable Output Patterns

{{:name}}                  Simple variable
{{:user.email}}            Object property
{{:users[0].name}}         Array element property
{{:data.items[2].value}}   Deep nested access
{{:value|filter}}          With filter
{{:value|filter,param}}    Filter with parameter
{{:value|f1|f2|f3}}        Chained filters

Expression Patterns

{{@a + b}}                 Arithmetic
{{@price * quantity}}      Multiplication
{{@(a + b) * c}}           With parentheses
{{@sqrt(16)}}              Function call
{{@length(name)}}          String function
{{@Upper(text)}}           Case conversion
{{@a + b|filter}}          Expression with filter

Conditional Patterns

{{if variable}}...{{endif}}
{{if !variable}}...{{endif}}
{{if value|eq,10}}...{{endif}}
{{if value|gt,0}}...{{else}}...{{endif}}
{{if @(a > b and c < d)}}...{{endif}}
{{if @(x = 1 or y = 2)}}...{{endif}}
{{if status|eq,"active"}}...{{elseif status|eq,"pending"}}...{{else}}...{{endif}}

Loop Patterns

{{for item in items}}
  {{:item.name}}
  {{:item.@@index}}        Loop index (1-based)
  {{if item.@@first}}...{{endif}}
  {{if item.@@last}}...{{endif}}
  {{if item.@@odd}}...{{endif}}
  {{if item.@@even}}...{{endif}}
{{endfor}}

{{for item in items}}
  {{:item.name}}
{{else}}
  No items found.
{{endfor}}

Dataset Field Iteration (for automatic forms)

{{# Iterate over dataset field metadata #}}
{{for f in dataset.fields}}
  FieldName: {{:f.FieldName}}
  DisplayLabel: {{:f.DisplayLabel}}
  DataType: {{:f.DataType}}
  Size: {{:f.Size}}
  Required: {{:f.Required}}
  ReadOnly: {{:f.ReadOnly}}
  Visible: {{:f.Visible}}
  Value: {{:f}}
{{endfor}}

{{# Generate form input based on field type #}}
{{for f in customers.fields}}
{{if f.Visible}}
  <label>{{:f.DisplayLabel}}{{if f.Required}} *{{endif}}</label>
  {{if f.DataType|eq,"ftInteger"}}
    <input type="number" name="{{:f.FieldName}}" value="{{:f}}">
  {{elseif f.DataType|eq,"ftDate"}}
    <input type="date" name="{{:f.FieldName}}" value="{{:f|datetostr,"yyyy-mm-dd"}}">
  {{else}}
    <input type="text" name="{{:f.FieldName}}" value="{{:f}}" maxlength="{{:f.Size}}">
  {{endif}}
{{endif}}
{{endfor}}

Set Statement Patterns

{{set count := 0}}                    Integer
{{set name := "John"}}                String
{{set active := true}}                Boolean
{{set price := 19.99}}                Float
{{set copy := :original}}             Copy variable
{{set upper := :name|uppercase}}      With filter
{{set total := @(price * qty)}}       With expression

Filter Quick Reference

Filter Syntax Description
uppercase {{:s|uppercase}} Convert to uppercase
lowercase {{:s|lowercase}} Convert to lowercase
capitalize {{:s|capitalize}} Capitalize words
trunc,n {{:s|trunc,50}} Truncate with “…”
truncate,n[,str] {{:s|truncate,50,"..."}} Truncate with custom ellipsis
lpad,n[,c] {{:s|lpad,10,"0"}} Left pad
rpad,n[,c] {{:s|rpad,10}} Right pad
default,val {{:s|default,"N/A"}} Fallback value
datetostr[,fmt] {{:d|datetostr,"yyyy-mm-dd"}} Format date
datetimetostr[,fmt] {{:d|datetimetostr}} Format datetime
formatfloat,fmt {{:n|formatfloat,"0.00"}} Format number
round,decimals {{:n|round,-2}} Round number
urlencode {{:s|urlencode}} URL encode
json {{:obj|json}} Serialize to JSON
htmlencode {{:s|htmlencode}} HTML encode
eq,val {{if x|eq,10}} Equal (for conditions)
ne,val {{if x|ne,0}} Not equal
gt,val {{if x|gt,5}} Greater than
ge,val {{if x|ge,18}} Greater or equal
lt,val {{if x|lt,100}} Less than
le,val {{if x|le,10}} Less or equal
contains,str {{if s|contains,"test"}} Contains substring
icontains,str {{if s|icontains,"TEST"}} Contains (case-insensitive)

Expression Functions

Function Example Result
sqrt(x) {{@sqrt(16)}} 4
abs(x) {{@abs(-5)}} 5
floor(x) {{@floor(3.7)}} 3
ceil(x) {{@ceil(3.2)}} 4
round(x,n) {{@round(3.456,-2)}} 3.46
min(a,b) {{@min(5,3)}} 3
max(a,b) {{@max(5,3)}} 5
length(s) {{@length("hello")}} 5
upper(s) {{@upper("hi")}} HI
lower(s) {{@lower("HI")}} hi
trim(s) {{@trim(" x ")}} x
left(s,n) {{@left("hello",2)}} he
right(s,n) {{@right("hello",2)}} lo

Template Inheritance Pattern

base.tpro:

<!DOCTYPE html>
<html>
<head><title>{{block "title"}}Default{{endblock}}</title></head>
<body>
{{block "content"}}{{endblock}}
</body>
</html>

page.tpro:

{{extends "base.tpro"}}
{{block "title"}}{{:page_title}}{{endblock}}
{{block "content"}}
<h1>{{:page_title}}</h1>
<p>{{:content}}</p>
{{endblock}}

Macro Pattern

{{macro button(text, style)}}
<button class="btn btn-{{:style}}">{{:text}}</button>
{{endmacro}}

{{>button("Save", "primary")}}
{{>button("Cancel", "secondary")}}

Whitespace Control

{{- :var}}      Strip whitespace BEFORE tag
{{:var -}}      Strip whitespace AFTER tag
{{- :var -}}    Strip whitespace BOTH sides

Delphi Integration Example

uses TemplatePro;

var
  Compiler: TTProCompiler;
  Template: ITProCompiledTemplate;
begin
  // Compile template
  Compiler := TTProCompiler.Create;
  try
    Template := Compiler.Compile('Hello {{:name}}!');
  finally
    Compiler.Free;
  end;

  // Set data and render
  Template.SetData('name', 'World');
  Template.SetData('items', MyObjectList);
  Template.SetData('user', UserObject);
  Template.SetData('data', MyDataSet);

  Result := Template.Render;
end;

Common Template Examples with Expected Output

Example 1: Simple variable output

Template: Hello {{:name}}!
Data: name = "World"
Output: Hello World!

Example 2: Loop with pseudo-variables

Template: {{for item in items}}{{:item.@@index}}. {{:item.name}}{{if !item.@@last}}, {{endif}}{{endfor}}
Data: items = [{name:"A"}, {name:"B"}, {name:"C"}]
Output: 1. A, 2. B, 3. C

Example 3: Conditional with filter

Template: Status: {{if active|eq,true}}Active{{else}}Inactive{{endif}}
Data: active = true
Output: Status: Active

Example 4: Expression with filter

Template: Total: ${{@price * quantity|formatfloat,"0.00"}}
Data: price = 19.99, quantity = 3
Output: Total: $59.97

Example 5: Set and accumulate

Template: {{set sum := 0}}{{for n in numbers}}{{set sum := @(sum + n)}}{{endfor}}Sum: {{:sum}}
Data: numbers = [1, 2, 3, 4, 5]
Output: Sum: 15

Example 6: Macro usage

Template: {{macro greet(name)}}Hello, {{:name}}!{{endmacro}}{{>greet("Alice")}} {{>greet("Bob")}}
Output: Hello, Alice! Hello, Bob!

Getting Started

Installation

Add the TemplatePro unit to your project:

uses
  TemplatePro;

Basic Usage

The workflow is simple: compile a template, set data, render output.

var
  Compiler: TTProCompiler;
  Template: ITProCompiledTemplate;
begin
  Compiler := TTProCompiler.Create;
  try
    Template := Compiler.Compile('Hello, {{:name}}!');
  finally
    Compiler.Free;
  end;

  Template.SetData('name', 'World');
  WriteLn(Template.Render);  // Output: Hello, World!
end;

Important: Always free the compiler after use. The compiled template (ITProCompiledTemplate) is reference-counted and managed automatically.

One-Line Rendering

For simple cases where you don’t need to reuse the template, use the static method:

var
  Output: string;
begin
  Output := TTProCompiler.CompileAndRender(
    'Hello, {{:name}}!',
    ['name'],
    ['World']
  );
  // Output: Hello, World!
end;

This method handles compiler creation and cleanup internally.

Compiled Template Caching

For better performance in production, save compiled templates to disk:

// Save compiled template
Template.SaveToFile('order_email.tpc');

// Load pre-compiled template (faster startup)
Template := TTProCompiledTemplate.CreateFromFile('order_email.tpc');

Note: Compiled templates are version-specific. Recompile when upgrading TemplatePro.


Template Syntax Overview

TemplatePro uses double curly braces {{ }} for all directives:

Syntax Purpose Example
{{:var}} Output a variable {{:customer.name}}
{{@expr}} Evaluate an expression {{@price * quantity}}
{{if}}...{{endif}} Conditional block {{if is_active}}...{{endif}}
{{for}}...{{endfor}} Loop block {{for item in items}}...{{endfor}}
{{set var := value}} Set a variable {{set total := 0}}
{{include "file"}} Include another template {{include "header.tpro"}}
{{macro name()}} Define a macro {{macro button(text)}}...{{endmacro}}
{{>name()}} Call a macro {{>button("Click")}}
{{# comment #}} Comment (not rendered) {{# TODO: fix this #}}
{{raw}}...{{endraw}} Output literal text {{raw}}{{:not parsed}}{{endraw}}
{{autoescape bool}} Control HTML encoding {{autoescape true}}...{{endautoescape}}
{{- ... -}} Whitespace control {{- :var -}} strips surrounding whitespace

Note: Since version 0.7.0, TemplatePro is case insensitive for both variable names and directives.


Variables

Simple Variables

Output variable values using the colon prefix:

Customer: {{:customer_name}}
Order Total: {{:order_total}}

Delphi code:

Template.SetData('customer_name', 'John Smith');
Template.SetData('order_total', 149.99);

Object Properties

Access nested properties using dot notation:

Customer: {{:order.customer.name}}
Address: {{:order.customer.address.city}}, {{:order.customer.address.country}}

Delphi code:

Template.SetData('order', OrderObject);  // TOrder with nested TCustomer

Array Elements

Access array elements by index (zero-based):

First item: {{:items[0].name}}
Second item: {{:items[1].name}}

Missing Variables

Missing or null variables render as empty strings without errors:

Middle name: {{:customer.middle_name}}

If middle_name is not set, nothing is rendered. Use the default filter for fallback values:

Middle name: {{:customer.middle_name|default,"(none)"}}

Expressions

Expressions allow calculations and function calls directly in templates. Use the @ prefix.

Arithmetic Operations

Subtotal: ${{@quantity * unit_price}}
Tax (10%): ${{@quantity * unit_price * 0.10}}
Total: ${{@quantity * unit_price * 1.10}}

Supported operators: +, -, *, /, div (integer division), mod, ^ (power)

Operator Precedence

Use parentheses to control evaluation order:

Without parentheses: {{@2 + 3 * 4}}       outputs 14
With parentheses: {{@(2 + 3) * 4}}        outputs 20

Built-in Functions

Math functions:

Function Description Example
sqrt(x) Square root {{@sqrt(16)}} → 4
abs(x) Absolute value {{@abs(-5)}} → 5
floor(x) Round down {{@floor(3.7)}} → 3
ceil(x) Round up {{@ceil(3.2)}} → 4
round(x, n) Round to n decimals {{@round(3.456, -2)}} → 3.46
min(a, b) Minimum value {{@min(5, 3)}} → 3
max(a, b) Maximum value {{@max(5, 3)}} → 5

String functions:

Function Description Example
length(s) String length {{@length("hello")}} → 5
upper(s) Uppercase {{@upper("hello")}} → HELLO
lower(s) Lowercase {{@lower("HELLO")}} → hello
trim(s) Remove whitespace {{@trim(" hi ")}} → hi
left(s, n) Left n characters {{@left("hello", 2)}} → he
right(s, n) Right n characters {{@right("hello", 2)}} → lo

Conversion functions:

Function Description Example
tostring(x) Convert to string {{@tostring(42)}} → “42”
tointeger(s) Convert to integer {{@tointeger("42")}} → 42

Expressions in Conditionals

Use @(condition) for complex boolean logic:

{{if @(age >= 18 and has_license)}}
  You can rent a car.
{{endif}}

{{if @(total > 100 or is_premium_member)}}
  Free shipping!
{{endif}}

Expressions with Filters

Since version 1.0, you can apply filters to expression results using the pipe syntax:

Subtotal: ${{@quantity * unit_price|formatfloat,"0.00"}}
Tax: ${{@quantity * unit_price * 0.10|formatfloat,"0.00"}}
Total: ${{@quantity * unit_price * 1.10|formatfloat,"0.00"}}

Chained filters on expressions:

Code: {{@value * 10|formatfloat,"0"|lpad,8,"0"}}

This evaluates value * 10, formats it as an integer, then left-pads to 8 characters with zeros.

Combining expression functions with filters:

Name: {{@Upper(name)|lpad,15}}

This applies the Upper() function inside the expression, then pads the result.

Note: Filters are applied to the entire expression result, not to individual parts. The expression {{@a + b|filter}} evaluates a + b first, then applies the filter to the sum.


The Set Statement

Define or modify variables within templates using {{set}}.

Syntax

{{set variable_name := value}}

Important: Use := (not =) for assignment, consistent with Delphi/Pascal syntax.

Literal Values

Supported literal types:

  • Strings: enclosed in double quotes "text"
  • Integers: 42, -10
  • Floats: 3.14, -0.5
  • Booleans: true or false (case-insensitive)

Note: true and false are reserved words and cannot be used as variable names.

{{set tax_rate := 0.10}}
{{set greeting := "Welcome!"}}
{{set is_active := true}}
{{set max_items := 100}}

Copying Variables

Copy an existing variable to a new one:

{{set backup_name := customer.name}}

Expression Values

Calculate values using expressions:

{{set subtotal := @(quantity * unit_price)}}
{{set tax := @(subtotal * tax_rate)}}
{{set total := @(subtotal + tax)}}

Order Summary:
  Subtotal: ${{:subtotal}}
  Tax: ${{:tax}}
  Total: ${{:total}}

Filtered Values

Apply filters to the assigned value:

{{set upper_name := customer.name|uppercase}}
{{set formatted_date := order.date|datetostr,"dd/mm/yyyy"}}

Accumulators in Loops

Build running totals:

{{set order_total := 0}}
{{for item in order.items}}
  {{:item.name}}: ${{:item.price}}
  {{set order_total := @(order_total + item.price)}}
{{endfor}}
Grand Total: ${{:order_total}}

Conditionals

Basic If/Else

{{if user.is_logged_in}}
  Welcome back, {{:user.name}}!
{{else}}
  Please log in to continue.
{{endif}}

Truthiness Rules

TemplatePro evaluates values as follows:

Type Truthy Falsy
Boolean true false
String Non-empty Empty string ""
Number Non-zero Zero 0
Object Not nil nil
Dataset Has records Empty (EOF)

Comparison Filters

Use filters for comparisons in conditions:

{{if order.total|gt,100}}
  Free shipping included!
{{endif}}

{{if stock_count|eq,0}}
  Out of stock
{{endif}}

{{if user.age|ge,18}}
  Adult content available
{{endif}}

Available comparison filters: eq, ne, gt, ge, lt, le, contains, icontains

Expression Conditions

For complex logic, use expression syntax:

{{if @(quantity > 0 and quantity <= stock)}}
  <button>Add to Cart</button>
{{else}}
  <button disabled>Unavailable</button>
{{endif}}

{{if @(is_member or total > 50)}}
  You qualify for free shipping!
{{endif}}

{{if @(status = "active" and days_remaining > 0)}}
  Subscription active
{{endif}}

Elseif / Elif

For multiple mutually exclusive conditions, use {{elseif}} or {{elif}}:

{{if status|eq,"premium"}}
  <span class="badge gold">Premium Member</span>
{{elseif status|eq,"standard"}}
  <span class="badge silver">Standard Member</span>
{{elseif status|eq,"trial"}}
  <span class="badge">Trial User</span>
{{else}}
  <span class="badge">Guest</span>
{{endif}}

Multiple elseif branches are supported:

{{if score|ge,90}}
  Grade: A
{{elseif score|ge,80}}
  Grade: B
{{elseif score|ge,70}}
  Grade: C
{{elseif score|ge,60}}
  Grade: D
{{else}}
  Grade: F
{{endif}}

Note: {{elseif}} and {{elif}} are interchangeable - use whichever you prefer.

Negation

Use ! to negate a condition:

{{if !user.email_verified}}
  Please verify your email address.
{{endif}}

Loops

Basic Loop

Iterate over collections with for...in:

<ul>
{{for product in products}}
  <li>{{:product.name}} - ${{:product.price}}</li>
{{endfor}}
</ul>

Pseudo-Variables

Access loop metadata using @@ prefix:

Variable Description
@@index Current iteration (1-based)
@@odd True for odd iterations (1, 3, 5…)
@@even True for even iterations (2, 4, 6…)
@@first True for the first iteration
@@last True for the last iteration (requires known count)
<table>
{{for item in items}}
  <tr class="{{if item.@@odd}}odd-row{{else}}even-row{{endif}}{{if item.@@first}} first{{endif}}{{if item.@@last}} last{{endif}}">
    <td>{{:item.@@index}}</td>
    <td>{{:item.name}}</td>
  </tr>
{{endfor}}
</table>

Note: @@last requires the collection to have a known count. For TDataSet record iteration, this requires the dataset to be fully fetched (RecordCount must be available). For field iteration, object lists, and JSON arrays, @@last works automatically.

Continue Statement

Skip iterations conditionally:

{{for product in products}}
  {{if product.is_hidden}}
    {{continue}}
  {{endif}}
  <div>{{:product.name}}</div>
{{endfor}}

Nested Loops

{{for category in categories}}
  <h2>{{:category.name}}</h2>
  <ul>
  {{for product in category.products}}
    <li>{{:product.name}}</li>
  {{endfor}}
  </ul>
{{endfor}}

Empty Collections (For-Else)

Handle empty collections elegantly with {{else}} inside the loop (Jinja2-style):

{{for order in orders}}
  Order #{{:order.id}}: ${{:order.total}}
{{else}}
  No orders found.
{{endfor}}

The {{else}} block executes only when the collection is empty. This is cleaner than wrapping the loop in an {{if}} block.

Another example - search results:

{{for result in results}}
  <div class="result">
    <a href="{{:result.url}}">{{:result.title}}</a>
  </div>
{{else}}
  <p>No results found for "{{:query}}".</p>
{{endfor}}

Filters

Filters transform values using the pipe | syntax.

Basic Usage

{{customer.name|uppercase}}
{{:product.description|trunc,100}}
{{order.date|datetostr,"dd/mm/yyyy"}}

Chained Filters

Apply multiple filters in sequence:

{{:user.email|lowercase|default,"no-email@example.com"}}
{{:product.code|uppercase|lpad,10,"0"}}

Built-in Filters Reference

Text Transformation:

Filter Description Example
uppercase Convert to uppercase {{:name|uppercase}}
lowercase Convert to lowercase {{:email|lowercase}}
capitalize Capitalize words {{:title|capitalize}}
trunc,n Truncate with ellipsis {{:text|trunc,50}}

Padding:

Filter Description Example
lpad,n[,char] Left-pad to length {{:id|lpad,6,"0"}} → 000042
rpad,n[,char] Right-pad to length {{:code|rpad,10,"-"}}

Date/Time:

Filter Description Example
datetostr[,format] Format date {{:date|datetostr,"yyyy-mm-dd"}}
datetimetostr[,format] Format datetime {{:timestamp|datetimetostr}}
formatdatetime,format Custom format {{:dt|formatdatetime,"dddd, mmmm d"}}

ISO 8601 Default Format: The datetostr filter defaults to yyyy-mm-dd and datetimetostr defaults to yyyy-mm-dd hh:nn:ss. This ensures consistent, locale-independent output. To use a different format, specify it as a parameter: {{:date|datetostr,"dd/mm/yyyy"}}.

ISO 8601 String Parsing: These filters also accept ISO 8601 date strings as input (e.g., 2025-12-30T14:23:08.281+01:00, 2025-12-30T14:23:08Z, 2025-12-30). This is useful when working with JSON data or web APIs. Empty strings return empty output without errors.

Numeric:

Filter Description Example
round,decimals Round number {{:price|round,-2}} → 19.99
formatfloat,format Format number {{:amount|formatfloat,"#,##0.00"}}
mod,n Modulo operation {{:num|mod,2}}

Default & Comparison:

Filter Description Example
default,value Fallback if empty/zero {{:nickname|default,"Guest"}}
eq,value Equal (for conditions) {{if status|eq,"active"}}
ne,value Not equal {{if type|ne,"admin"}}
gt,value Greater than {{if age|gt,17}}
ge,value Greater or equal {{if score|ge,60}}
lt,value Less than {{if stock|lt,10}}
le,value Less or equal {{if items|le,5}}
contains,text Contains substring {{if name|contains,"Jr"}}
icontains,text Contains (case-insensitive) {{if email|icontains,"gmail"}}

Utility:

Filter Description Example
totrue Always returns true {{if var|totrue}}
tofalse Always returns false {{if var|tofalse}}
urlencode URL-encode a string {{:query|urlencode}}
truncate,n[,ellipsis] Truncate to n chars with ellipsis {{:text|truncate,50}}
json Serialize object to JSON {{:obj|json}}

Functions (applied to empty value):

Filter Description Example
version TemplatePro version {{:|version}}

Custom Filters

Register custom transformation functions:

// Define the filter function
function CurrencyFilter(const Value: TValue;
  const Params: TArray<TFilterParameter>): TValue;
begin
  Result := Format('$%.2f', [Value.AsExtended]);
end;

// Register after compilation
Template.AddFilter('currency', CurrencyFilter);

Template usage:

Price: {{:product.price|currency}}

Filter with parameters:

function RepeatFilter(const Value: TValue;
  const Params: TArray<TFilterParameter>): TValue;
var
  i, Count: Integer;
  S: string;
begin
  Count := Params[0].ParIntValue;
  S := '';
  for i := 1 to Count do
    S := S + Value.AsString;
  Result := S;
end;

Template.AddFilter('repeat', RepeatFilter);

Usage:

{{:"-"|repeat,20}}  outputs: --------------------

Macros

Macros are reusable template fragments with parameters.

Definition

{{macro alert(message, type)}}
<div class="alert alert-{{:type}}">
  {{:message}}
</div>
{{endmacro}}

Calling Macros

Use the {{>}} syntax to invoke macros:

{{>alert("Operation successful!", "success")}}
{{>alert("Please check your input.", "warning")}}
{{>alert("An error occurred.", "error")}}

Macros Without Parameters

{{macro divider()}}
<hr class="section-divider">
{{endmacro}}

{{>divider()}}

Macros with Variables

Pass template variables as arguments:

{{macro user_card(user)}}
<div class="user-card">
  <h3>{{:user.name}}</h3>
  <p>{{:user.email}}</p>
</div>
{{endmacro}}

{{for u in users}}
  {{>user_card(u)}}
{{endfor}}

Nested Macros

Macros can call other macros:

{{macro icon(name)}}
<i class="icon icon-{{:name}}"></i>
{{endmacro}}

{{macro button(text, icon_name)}}
<button class="btn">
  {{>icon(icon_name)}} {{:text}}
</button>
{{endmacro}}

{{>button("Save", "check")}}
{{>button("Delete", "trash")}}

Template Composition

Include

Include external template files:

{{include "partials/header.tpro"}}

<main>
  <h1>{{:page_title}}</h1>
  {{:content}}
</main>

{{include "partials/footer.tpro"}}

Include with Variable Mappings

Pass variables to included templates:

{{include "components/product_card.tpro", product = :item, show_price = true}}

product_card.tpro:

<div class="product-card">
  <h3>{{:product.name}}</h3>
  {{if show_price}}
    <p class="price">${{:product.price}}</p>
  {{endif}}
</div>

You can pass different types:

{{include "alert.tpro", message = "Success!", type = "success"}}
{{include "alert.tpro", message = :error_message, type = "error"}}
{{include "counter.tpro", count = @(items_count * 2)}}

Dynamic Include

Include templates based on variables:

{{include :template_name}}

Template Inheritance

Create a base layout with replaceable blocks:

base.tpro:

<!DOCTYPE html>
<html>
<head>
  <title>{{block "title"}}My Site{{endblock}}</title>
</head>
<body>
  <nav>{{block "navigation"}}Default nav{{endblock}}</nav>

  <main>
    {{block "content"}}{{endblock}}
  </main>

  <footer>{{block "footer"}}Copyright 2025{{endblock}}</footer>
</body>
</html>

page.tpro:

{{extends "base.tpro"}}

{{block "title"}}{{:page_title}} - My Site{{endblock}}

{{block "content"}}
<h1>{{:page_title}}</h1>
<p>{{:content}}</p>
{{endblock}}

Inheritance rules:

  • Child templates can only extend one parent
  • Blocks not defined in the child use the parent’s content
  • Content outside blocks in child templates is ignored

The inherited Keyword

Use {{inherited}} to include parent block content while adding your own:

base.tpro:

<head>
  {{block "head"}}
  <meta charset="UTF-8">
  <title>My Site</title>
  {{endblock}}
</head>

page.tpro:

{{extends "base.tpro"}}

{{block "head"}}
{{inherited}}
<link rel="stylesheet" href="page.css">
<script src="page.js"></script>
{{endblock}}

Output:

<head>
  <meta charset="UTF-8">
  <title>My Site</title>
  <link rel="stylesheet" href="page.css">
  <script src="page.js"></script>
</head>

Multi-level Inheritance

Templates can extend other templates that themselves extend other templates, creating unlimited inheritance chains:

level1_base.tpro:

<html>
{{block "content"}}Base Content{{endblock}}
</html>

level2_section.tpro:

{{extends "level1_base.tpro"}}
{{block "content"}}Section: {{inherited}}{{endblock}}

level3_page.tpro:

{{extends "level2_section.tpro"}}
{{block "content"}}Page -> {{inherited}}{{endblock}}

Output:

<html>
Page -> Section: Base Content
</html>

Multi-level inheritance features:

  • {{inherited}} includes content from the immediate parent at each level
  • Circular inheritance is detected and raises a clear error
  • Self-inheritance (a template extending itself) is also detected
  • Works correctly with partial block overrides at any level

Working with Datasets

TemplatePro integrates seamlessly with Delphi TDataSet components, providing both record iteration and field metadata access for dynamic UI generation.

Iterating Records

<table>
  <tr><th>Code</th><th>Name</th><th>Price</th></tr>
  {{for product in products}}
  <tr>
    <td>{{:product.Code}}</td>
    <td>{{:product.Name}}</td>
    <td>{{:product.Price|formatfloat,"0.00"}}</td>
  </tr>
  {{endfor}}
</table>

Delphi code:

Template.SetData('products', ProductsDataSet);

Field Metadata Iteration

TemplatePro can iterate over dataset field definitions using the .fields suffix. This enables automatic form generation, dynamic table headers, and metadata-driven UIs.

Syntax: {{for field in dataset.fields}}...{{endfor}}

Field Properties Reference

When iterating fields, each field object exposes these properties:

Property Type Description Example
FieldName String Internal field name (database column) CUSTOMER_ID
DisplayLabel String User-friendly label for UI Customer ID
DisplayName String Display name Customer ID
DataType String Field data type ftString, ftInteger, ftDate
Size Integer Field size (for strings) 50
Index Integer Field index (0-based) 0, 1, 2
Required Boolean Field is required (NOT NULL) true/false
ReadOnly Boolean Field is read-only true/false
Visible Boolean Field is visible true/false
IsNull Boolean Current value is NULL true/false
AsString String Current value as string "John"
(no property) String {{:field}} returns current value "John"

Complete Example: Automatic HTML Form Generation

This example generates a complete HTML form from dataset metadata, with proper input types, validation attributes, and Bootstrap styling:

{{# Automatic Form Generator - uses dataset field metadata #}}
<form method="post" action="/save" class="needs-validation" novalidate>
  <h2>Edit {{:formtitle}}</h2>

  {{for f in customers.fields}}
  {{if f.Visible}}
  <div class="mb-3">
    {{# Label with required indicator #}}
    <label for="{{:f.FieldName}}" class="form-label">
      {{:f.DisplayLabel}}{{if f.Required}} <span class="text-danger">*</span>{{endif}}
    </label>

    {{# Input type based on DataType #}}
    {{if f.DataType|eq,"ftInteger"}}
    <input type="number"
           class="form-control"
           id="{{:f.FieldName}}"
           name="{{:f.FieldName}}"
           value="{{:f}}"
           {{if f.Required}}required{{endif}}
           {{if f.ReadOnly}}readonly{{endif}}>

    {{elseif f.DataType|eq,"ftFloat"}}
    <input type="number"
           step="0.01"
           class="form-control"
           id="{{:f.FieldName}}"
           name="{{:f.FieldName}}"
           value="{{:f}}"
           {{if f.Required}}required{{endif}}
           {{if f.ReadOnly}}readonly{{endif}}>

    {{elseif f.DataType|eq,"ftDate"}}
    <input type="date"
           class="form-control"
           id="{{:f.FieldName}}"
           name="{{:f.FieldName}}"
           value="{{:f|datetostr,"yyyy-mm-dd"}}"
           {{if f.Required}}required{{endif}}
           {{if f.ReadOnly}}readonly{{endif}}>

    {{elseif f.DataType|eq,"ftDateTime"}}
    <input type="datetime-local"
           class="form-control"
           id="{{:f.FieldName}}"
           name="{{:f.FieldName}}"
           value="{{:f|datetimetostr,"yyyy-mm-ddThh:nn"}}"
           {{if f.Required}}required{{endif}}
           {{if f.ReadOnly}}readonly{{endif}}>

    {{elseif f.DataType|eq,"ftBoolean"}}
    <div class="form-check">
      <input type="checkbox"
             class="form-check-input"
             id="{{:f.FieldName}}"
             name="{{:f.FieldName}}"
             value="1"
             {{if f}}checked{{endif}}
             {{if f.ReadOnly}}disabled{{endif}}>
      <label class="form-check-label" for="{{:f.FieldName}}">{{:f.DisplayLabel}}</label>
    </div>

    {{elseif f.DataType|eq,"ftMemo"}}
    <textarea class="form-control"
              id="{{:f.FieldName}}"
              name="{{:f.FieldName}}"
              rows="4"
              {{if f.Required}}required{{endif}}
              {{if f.ReadOnly}}readonly{{endif}}>{{:f}}</textarea>

    {{else}}
    {{# Default: text input with maxlength from Size #}}
    <input type="text"
           class="form-control"
           id="{{:f.FieldName}}"
           name="{{:f.FieldName}}"
           value="{{:f}}"
           {{if f.Size|gt,0}}maxlength="{{:f.Size}}"{{endif}}
           {{if f.Required}}required{{endif}}
           {{if f.ReadOnly}}readonly{{endif}}>
    {{endif}}

    {{# Help text showing field constraints #}}
    {{if f.Size|gt,0}}
    <div class="form-text">Max {{:f.Size}} characters</div>
    {{endif}}
  </div>
  {{endif}}
  {{endfor}}

  <div class="mt-4">
    <button type="submit" class="btn btn-primary">Save</button>
    <a href="/list" class="btn btn-secondary">Cancel</a>
  </div>
</form>

Delphi code to use this template:

var
  Template: ITProCompiledTemplate;
begin
  // Load and compile template
  Template := TTProCompiler.CreateFromFile('customer_form.tpro');

  // Pass the dataset - TemplatePro reads both data and metadata
  Template.SetData('customers', CustomersDataSet);
  Template.SetData('formtitle', 'Customer');

  // Render the form
  Result := Template.Render;
end;

Dynamic Table with Field Headers

Generate a complete data table with headers from field metadata:

<table class="table table-striped">
  <thead>
    <tr>
      {{for f in data.fields}}
      {{if f.Visible}}
      <th>{{:f.DisplayLabel}}</th>
      {{endif}}
      {{endfor}}
    </tr>
  </thead>
  <tbody>
    {{for row in data}}
    <tr>
      {{for f in data.fields}}
      {{if f.Visible}}
      <td>
        {{if f.DataType|eq,"ftDate"}}
          {{:row[f.FieldName]|datetostr}}
        {{elseif f.DataType|eq,"ftFloat"}}
          {{:row[f.FieldName]|formatfloat,"#,##0.00"}}
        {{else}}
          {{:row[f.FieldName]}}
        {{endif}}
      </td>
      {{endif}}
      {{endfor}}
    </tr>
    {{else}}
    <tr><td colspan="100">No records found.</td></tr>
    {{endfor}}
  </tbody>
</table>

Expression Conditions with Fields

Use expressions to check field properties:

{{for f in customers.fields}}
  {{if @(f.Index = 0)}}
    <th>{{:f.DisplayLabel}}</th>  {{# First field as header #}}
  {{else}}
    <td>{{:f}}</td>
  {{endif}}
{{endfor}}

Field Iteration Pseudo-Variables

Field loops support the same pseudo-variables as regular loops:

{{for f in data.fields}}
  {{:f.@@index}}. {{:f.FieldName}}
  {{if f.@@first}}(first){{endif}}
  {{if f.@@last}}(last){{endif}}
{{endfor}}

Output Configuration

Line Endings

TemplatePro provides cross-platform control over output line endings.

Input: Templates with any line ending style (CRLF, LF, CR) are automatically recognized.

Output: Configure the output line ending style:

Template.OutputLineEnding := lesLF;      // Unix-style (default)
Template.OutputLineEnding := lesCRLF;    // Windows-style
Template.OutputLineEnding := lesCR;      // Classic Mac
Template.OutputLineEnding := lesNative;  // Platform default

Available values:

Value Description Characters
lesLF Unix/Linux/Modern Mac #10
lesCRLF Windows #13#10
lesCR Classic Mac #13
lesNative Current platform default varies

Tip: Use lesLF (the default) for maximum portability. Most modern systems handle LF correctly.


API Reference

TTProCompiler

The template compiler.

var
  Compiler: TTProCompiler;
begin
  Compiler := TTProCompiler.Create;
  try
    // Compile from string
    Template := Compiler.Compile(TemplateString);

    // Compile from string with file path reference (for includes)
    Template := Compiler.Compile(TemplateString, 'templates/page.tpro');
  finally
    Compiler.Free;
  end;
end;

Static methods:

// Compile and render in one call
Output := TTProCompiler.CompileAndRender(
  'Hello {{:name}}!',
  ['name'],
  ['World']
);

ITProCompiledTemplate

The compiled template interface.

Setting data:

Template.SetData('name', 'John');              // String
Template.SetData('age', 30);                   // Integer
Template.SetData('price', 19.99);              // Float
Template.SetData('is_active', True);           // Boolean
Template.SetData('user', UserObject);          // Object
Template.SetData('items', ItemsList);          // TObjectList
Template.SetData('customers', CustomersDataSet); // TDataSet

Adding filters:

Template.AddFilter('myfilter', MyFilterFunction);
Template.AddFilter('myfilter',
  function(const Value: TValue; const Params: TArray<TFilterParameter>): TValue
  begin
    Result := '[' + Value.AsString + ']';
  end
);

Rendering:

Output := Template.Render;

Persistence:

Template.SaveToFile('template.tpc');
Template := TTProCompiledTemplate.CreateFromFile('template.tpc');

Properties:

Template.OutputLineEnding := lesLF;
Template.FormatSettings := @MyFormatSettings;

Exceptions

try
  Template := Compiler.Compile(TemplateString);
  Output := Template.Render;
except
  on E: ETProCompilerException do
    // Syntax errors, missing end tags, invalid directives
    ShowMessage('Compilation error: ' + E.Message);

  on E: ETProRenderException do
    // Runtime errors: missing variables, type mismatches
    ShowMessage('Render error: ' + E.Message);

  on E: ETProException do
    // General TemplatePro errors
    ShowMessage('Template error: ' + E.Message);
end;

Best Practices

Template Organization

templates/
  layouts/
    base.tpro
    admin.tpro
  partials/
    header.tpro
    footer.tpro
    navigation.tpro
  components/
    alert.tpro
    card.tpro
    form_field.tpro
  pages/
    home.tpro
    products.tpro
    checkout.tpro
  emails/
    order_confirmation.tpro
    password_reset.tpro

Performance Tips

  1. Compile once, render many: Keep compiled templates for reuse.
  2. Use pre-compiled templates: Save .tpc files for production.
  3. Avoid complex logic in templates: Pre-process data in Delphi.
  4. Use appropriate data structures: TObjectList over arrays for large collections.

Security

  1. Escape user input: Be cautious with user-provided data in templates.
  2. Validate include paths: Don’t allow user-controlled template paths.
  3. Limit template complexity: Set reasonable limits on nesting depth.

Debugging

// Dump compiled tokens for debugging
Template.DumpToFile('debug_output.txt');

// Use the OnGetValue event for custom variable resolution
Template.OnGetValue := procedure(const DataSource, Members: string;
  var Value: TValue; var Handled: Boolean)
begin
  WriteLn('Accessing: ', DataSource, '.', Members);
end;

Autoescape Block

Control HTML encoding for a section of the template.

Default Behavior: HTML Encoding is ON

By default, all variable output is HTML-encoded for security (XSS prevention). Special characters like <, >, &, " are converted to HTML entities.

{{:myhtml}}

If myhtml contains <div>Hello</div>, output will be: &lt;div&gt;Hello&lt;/div&gt;

Disabling Autoescape for a Block

Use {{autoescape false}} to output raw HTML for trusted content:

{{autoescape false}}
{{:trusted_html}}
{{endautoescape}}

Explicit Raw Output with $ Suffix

For a single variable, use the $ suffix to skip HTML encoding:

Encoded (default): {{:myhtml}}
Raw (explicit): {{:myhtml$}}

This is useful when you need raw output for just one variable without changing the autoescape mode.

Complete Example

** Autoescape Demo **
Default (encoded): {{:myhtml}}
{{autoescape false}}
Autoescape off: {{:myhtml}}
{{endautoescape}}
Back to default: {{:myhtml}}
Explicit raw: {{:myhtml$}}

With myhtml = "<b>Bold</b>":

** Autoescape Demo **
Default (encoded): &lt;b&gt;Bold&lt;/b&gt;
Autoescape off: <b>Bold</b>
Back to default: &lt;b&gt;Bold&lt;/b&gt;
Explicit raw: <b>Bold</b>

Security Note: Only disable autoescape or use $ suffix for trusted content. User-provided data should always be encoded to prevent XSS attacks.


Raw Blocks

Output template syntax literally without parsing using {{raw}}...{{endraw}}.

Basic Usage

Normal output: {{:value1}}

{{raw}}
This is literal: {{:value1}} {{for x in items}}{{endfor}}
{{endraw}}

Back to normal: {{:value1}}

Output:

Normal output: true

This is literal: {{:value1}} {{for x in items}}{{endfor}}

Back to normal: true

Use Cases

  • Documenting TemplatePro syntax in templates
  • Outputting JavaScript template literals that use similar syntax
  • Embedding code examples in generated documentation

Whitespace Control

Control whitespace around template tags using - modifiers.

Syntax

Modifier Effect
{{- ... }} Strip whitespace before the tag
{{ ... -}} Strip whitespace after the tag
{{- ... -}} Strip whitespace on both sides

Examples

Before{{- :value -}}After

Output: BeforetrueAfter (no spaces)

Left strip:   {{- :value}}END

Output: Left strip:trueEND (spaces before tag removed)

Right strip:{{:value -}}   END

Output: Right strip:trueEND (spaces after tag removed)

Use in Loops

Whitespace control is especially useful in loops to avoid unwanted blank lines:

<ul>
{{- for item in items -}}
  <li>{{:item.name}}</li>
{{- endfor -}}
</ul>

Comments

Add comments that won’t appear in output:

{{# This is a single-line comment #}}

{{#
  This is a multi-line comment.
  It can span several lines.
#}}

Flow Control

Exit Statement

Stop rendering the template:

{{if critical_error}}
  <p>A critical error occurred.</p>
  {{exit}}
{{endif}}

{{# This content won't render if there's an error #}}
<p>Normal content here.</p>

Migration Notes

From Version 0.8.x

Set syntax change:

{{# Old syntax (0.8.x) #}}
{{set myvar = 42}}

{{# New syntax (1.0) #}}
{{set myvar := 42}}

And/Or filters removed:

{{# Old syntax (deprecated) #}}
{{if value1|and,value2}}

{{# New syntax #}}
{{if @(value1 and value2)}}

Features in 1.0:

  • {{inherited}} keyword for including parent block content
  • Multi-level template inheritance (A extends B extends C)
  • Circular inheritance detection
  • {{elseif}}/{{elif}} conditional branches
  • Date filters default to ISO 8601 format (yyyy-mm-dd / yyyy-mm-dd hh:nn:ss)
  • Date filters accept ISO 8601 strings from JSON/APIs
  • {{autoescape true/false}}...{{endautoescape}} for HTML encoding control
  • {{raw}}...{{endraw}} for literal output without parsing
  • Whitespace control with {{- and -}} modifiers
  • New filters: urlencode, truncate, json

Frequently Asked Questions (FAQ)

What is the difference between TemplatePro and other Delphi template engines?

TemplatePro offers a Jinja2/Smarty-inspired syntax that is familiar to web developers, while being specifically optimized for Delphi. Unlike simpler string replacement engines, TemplatePro supports:

  • Full expression evaluation
  • Template inheritance
  • Macros with parameters
  • Native TDataSet support
  • Compiled template caching

Can I use TemplatePro with DMVCFramework?

Yes! TemplatePro is the default template engine for DMVCFramework. It integrates seamlessly for server-side HTML rendering in web applications.

Is TemplatePro thread-safe?

Yes. Compiled templates (ITProCompiledTemplate) are thread-safe for rendering. You can compile once and render from multiple threads simultaneously.

What data types can I pass to templates?

TemplatePro supports:

  • Simple types (string, integer, float, boolean)
  • Objects with published properties
  • TObjectList<T> and other generic lists
  • TDataSet and descendants
  • JSON objects (TJDOJsonObject)
  • Nullable types from MVCFramework.Nullables

How do I handle HTML escaping?

By default, all variable output IS HTML-encoded for security (XSS prevention). Characters like <, >, &, " are automatically converted to HTML entities.

To output raw HTML for trusted content only, use:

{{:trusted_html$}}              {{# $ suffix = raw output #}}
{{autoescape false}}
  {{:trusted_block}}            {{# block with autoescape off #}}
{{endautoescape}}

Security: Never disable autoescape for user-provided data.

Can I extend TemplatePro with custom filters?

Yes! Register custom filters using TTProConfiguration.OnContextConfiguration:

TTProConfiguration.OnContextConfiguration :=
  procedure(const Template: ITProCompiledTemplate)
  begin
    Template.AddTemplateFunction('myfilter', MyFilterFunction);
  end;

What’s the performance like?

TemplatePro compiles templates to an internal token representation. Rendering is very fast:

  • Compilation: ~1ms for typical templates
  • Rendering: ~0.1ms for simple templates, scales linearly with complexity
  • Memory: Minimal footprint, compiled templates are lightweight

Does TemplatePro support internationalization (i18n)?

TemplatePro provides locale-aware formatting through FormatSettings. For full i18n, combine with your localization framework and pass translated strings as variables.



TemplatePro Version 1.0 - Updated January 2026

Comments

comments powered by Disqus