Example:
  • CSV-structured template → CSV output
  • XML-structured template → XML output
  • HTML-structured template → HTML or PDF output
  • TXT-structured template → TXT output
Test Reporter locally without deploying to Kubernetes using our plugins-docker-compose repository.Keep in mind that these services require a valid license to run. Without it, the application will not start. For license details, check our License documentation.

Why use Reporter?

Instead of writing complex SQL queries, you can use intuitive placeholders that reference your domains, tables, and fields directly. It’s a fast, flexible, and maintainable way to create reports that evolve alongside your data model.

What it can do

  • Dynamic queries with placeholders: Reference any data point via direct paths — no SQL required.
  • Loop and condition logic: Support for loops, conditionals, and scoped blocks.
  • Math operations and aggregation: Perform calculations with sum_by, avg_by, and the new {% calc %} block.
  • Async processing: Heavy reports are handled asynchronously to protect live workloads.
  • Multiple output formats: Generate CSV, XML, HTML, TXT, or PDF documents depending on your needs.

Template Model

Reporter uses templates that mirror the final document structure. Whether you’re generating a structured XML file or a printable PDF, the content will match exactly.
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 <item> in <list> %}
  ...
{% 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 <object> as <alias> %}
  ...
{% endwith %}
  • Value formatting
Allows you to 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
{{{ field_name }}|floatformat:2 }}  ➔ renders 123.45

Conditional blocks

Reporter support rich logical conditions:
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
<Sum>
  {% sum_by transaction.operation by "amount" if accountAlias != "@external/BRL" %}
</Sum>
Assuming the following data:
"transaction": {
  "operation": [
    { "value": "10.00", "accountAlias": "@user/BRL" },
    { "value": "5.00", "accountAlias": "@external/BRL" },
    { "value": "25.00", "accountAlias": "@user/BRL" }
  ]
}
The output will be:
<Sum>35.00</Sum>

Other functions

  • Count
<Count>
  {% count_by transaction.operation if accountAlias != "@external/BRL" %}
</Count>
  • Average
<Average>
  {% avg_by transaction.operation by "amount" if accountAlias != "@external/BRL" %}
</Average>
  • Minimum
<Minimum>
  {% min_by transaction.operation by "amount" if accountAlias != "@external/BRL" %}
</Minimum>
  • Maximum
<Maximum>
  {% max_by transaction.operation by "amount" if accountAlias != "@external/BRL" %}
</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 = "6.00"
  • total.expenses = "20.00"
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.
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.accountAlias, 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

Reporter 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.
filter(list, "field", value)Filters lists as if it were a WHERE clause.
{% calc %}Executes inline mathematical expressions to dynamically compute values during template rendering. Supports arithmetic operations, nested expressions, and variable references — ideal for calculations like totals, balances, or derived metrics. Example: {% calc (balance.available + 1.2) * balance.on_hold - balance.available / 2 %}

Advanced filtering

Reporter support complex filtering logic in the filters object of a report request. Each filter is applied per datasource > table > field, using a condition like:
{
  "filters": {
    "midaz_onboarding": {
      "account": {
        "id": { "eq": ["123", "456"] },
        "createdAt": { "between": ["2023-01-01", "2023-01-31"] },
        "status": { "in": ["active", "pending"] }
      }
    }
  }
}

Supported operators:

  • eq: Equal to;
  • gt: Greater than;
  • gte: Greater than or equal to;
  • lt: Less than;
  • lte: Less than or equal to;
  • between: Value falls within a range;
  • in: Value is within a list;
  • nin: Value is not within a list.

Security recommendations

Security is foundational when working with Lerian products and plugins.
Before deploying any component into your environment, we strongly recommend reviewing our Security Recommendations. Each product—along with its associated plugins—should be implemented in line with security best practices, including:
  • Securing network boundaries
  • Managing and rotating secrets
  • Applying timely patch management
  • Enforcing strict role-based access controls (RBAC)
By following these practices, you ensure that Lerian’s products and plugins operate securely, integrate seamlessly with your infrastructure, and uphold compliance, data integrity, and user trust across your entire ecosystem.

Need inspiration?

Check out the Template examples page to explore what you can do and start shaping your own template.