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

BlockDescriptionExample
IfRuns a block if the condition is true{% if condition %}...{% endif %}
If-elseRuns one block if true, another if false{% if condition %}...{% else %}...{% endif %}
If - else - ifAllows multiple checks within the same block{% if a %}...{% elif b %}...{% else %}...{% endif %}
EqualChecks if two values are equal{% if a == b %}
Not equalChecks if two values are different{% if a != b %}
Greater thanChecks if a is greater than b{% if a > b %}
Less thanChecks if a is less than b{% if a < b %}
Greater than or equalChecks if a is greater than or equal to b{% if a >= b %}
Less than or equalChecks if a is less than or equal to b{% if a <= b %}
AndReturns true if both conditions are true{% if a and b %}
OrReturns true if at least one condition is true{% if a or b %}
NotInverts 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.

KeywordDescription
sum_bySum values
-Subtract
*Multiply
/Divide
avg_byCalculate average
min_byMinimum value
max_byMaximum value
percent_ofCalculates percentage
scale:xFormats 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.