Smart Templates
The Smart Templates plugin was created to help Midaz Ledger users generate dynamic, data-driven reports using plain-text templates (.tpl
files). It interprets these templates and renders reports in CSV, XML, HTML, or TXT, always matching the structure defined in the original file.
Why use the Smart Templates plugin?
Instead of writing complex queries, you can use simple placeholders to reference domains, tables, and fields directly. It’s a fast and flexible way to build reports that stay up to date, even when your data model evolves.
What it can do
- Dynamic queries with placeholders: Templates use simple placeholders to pull data directly from the database; no SQL is required.
- Loop and condition logic: Add loops and conditions to handle more complex scenarios without complicating things.
- Async processing: Large reports are processed asynchronously so that they won’t interfere with your live workloads.
- Multiple output formats: Generate reports in CSV, XML, HTML, or TXT — whichever fits your use case.
Why it matters
- No code required: Create powerful reports with simple templates.
- Flexible: Your templates adapt automatically as the data model evolves.
- Optimized performance: Runs on a replicated read database to avoid slowdowns.
Template model
The Smart Templates plugin reads template files that follow the structure of the final output, whether it’s XML, CSV, HTML, or TXT. The generated report will match this structure exactly.
Example
- XML-structured template = XML output
- CSV-structured template = CSV output
- HTML-structured template = HTML output
- TXT-structured template = TXT output
Important
Even though the file content must follow the output format, make sure to save it with a
.tpl
extension. This is required for the template to work properly.
Setting up your environment
Before starting, you must rename the databases used as references for searching for information while rendering reports. This configuration must be done in the project's .env
file.
By defining unique and clear names for each database, we avoid conflicts, especially in cases where different databases have tables with the same names, and we ensure better fluidity in queries.
Example
If you are using a database called onboarding
, we recommend renaming it to something more descriptive, such as:
midaz_onboarding
(PostgreSQL)midaz_onboarding_metadata
(MongoDB)
Using placeholders
Templates use placeholders to fetch data dynamically. A placeholder points to a field inside a table or a document collection, and it follows this structure:
{{ base.table_or_collection.field_or_document }}
This format works for SQL databases (tables) and MongoDB (collections), giving you full flexibility regardless of your data model.
Building templates
You can enhance your templates with conditionals, math operators, and temporary variables, making them more dynamic and flexible.
Common blocks
Loop
Iterates through a set of data and renders values inside the block. While it may look like a conditional, it's a repetition structure.
Example
{% for <condition> %}
....
{% endfor %}
Simple condition
Checks a logical rule. If it's true, the block is executed.
Example
{% if value_a == value_b %}
...
{% endif %}
Temporary scope
Creates a temporary variable inside the block. This makes templates easier to read and avoids repeating long paths (like accessing the same object or field multiple times).
Example
{% with ... %}
...
{% endwith %}
Value formatting
Lets you display numbers with a fixed number of decimal places, perfect for keeping financial values clear and consistent.
- Add a number after the colon to define the decimal precision. For example,
:2
shows two decimal places:123.45
.
Example
{{{placeholder}}|floatformat:2}
Conditional blocks
Block | Description | Example |
---|---|---|
If | Runs a block if the condition is true | {% if condition %}...{% endif %} |
If-else | Runs one block if true, another if false | {% if condition %}...{% else %}...{% endif %} |
If - else - if | Allows multiple checks within the same block | {% if a %}...{% elif b %}...{% else %}...{% endif %} |
Equal | Checks if two values are equal | {% if a == b %} |
Not equal | Checks if two values are different | {% if a != b %} |
Greater than | Checks if a is greater than b | {% if a > b %} |
Less than | Checks if a is less than b | {% if a < b %} |
Greater than or equal | Checks if a is greater than or equal to b | {% if a >= b %} |
Less than or equal | Checks if a is less than or equal to b | {% if a <= b %} |
And | Returns true if both conditions are true | {% if a and b %} |
Or | Returns true if at least one condition is true | {% if a or b %} |
Not | Inverts the Boolean result of a condition | {% if not a %} |
Operators and filters
You can combine logical conditions with math operations to define rules, calculate values, and format results with precision.
Example: Calculating Total Sum with Filter and Scale
<Sum>
{% sum_by transaction.operation by "amount" if account_alias != "@external/BRL" scale 2 %}
</Sum>
Assuming the following data:
"transaction": {
"operation": [
{ "amount": 1000, "account_alias": "@user/BRL" },
{ "amount": 500, "account_alias": "@external/BRL" },
{ "amount": 2500, "account_alias": "@user/BRL" }
]
}
The output will be:
<Sum>35.00</Sum>
Other functions
Count
<Count>
{% count_by transaction.operation if account_alias != "@external/BRL" %}
</Count>
Average
<Average>
{% avg_by transaction.operation by "amount" if account_alias != "@external/BRL" scale 2 %}
</Average>
Minimum
<Minimum>
{% min_by transaction.operation by "amount" if account_alias != "@external/BRL" scale 2 %}
</Minimum>
Maximum
<Maximum>
{% max_by transaction.operation by "amount" if account_alias != "@external/BRL" scale 2 %}
</Maximum>
Percentage
Let’s say the user is generating a monthly expense report and wants to show how much of the total was spent on "Food":
<Percentage>
{{ category.amount | percent_of: total.expenses }}
</Percentage>
If:
category.amount = 600
total.expenses = 2000
Output:
<Percentage>
30.00%
</Percentage>
Date
Applies the current date when rendering the template.
You can format it however you need (ddMMyyyy
, yyyyMMdd
, and more). Time can be included too, like in the example below.
Note
The time is generated in UTC (Coordinated Universal Time), without regional time zones or daylight saving adjustments.
Input:
{% date_time "ddMMYYYY HH:mm"}
Output:
13-05-2025 21:15
Contains
Use this filter to check if one value is partially included in another, even if they’re not identical. It’s beneficial when your data includes dynamic prefixes or suffixes, like in account aliases.
{% if contains(midaz_transaction.transaction.body.source.from.account_alias, midaz_onboarding.account.alias) %}
Example:
midaz_transaction.transaction.body.source.from.account_alias = 0#@external/BRL
midaz_onboarding.account.alias = @external/BRL
Even though the strings don’t match exactly, contains
will return true
because @external/BRL
is inside 0#@external/BRL
.
Math operations
Allows you to perform calculations on exported data.
Keyword | Description |
---|---|
sum_by | Sum values |
- | Subtract |
* | Multiply |
/ | Divide |
avg_by | Calculate average |
min_by | Minimum value |
max_by | Maximum value |
percent_of | Calculates percentage |
scale:x | Formats values based on scale |
filter(list, "field", value) | Filters lists as if it were a WHERE clause |
Need inspiration?
Check out the Template examples page to explore what you can do and start shaping your own template.
Updated 4 days ago