.tpl files). Templates render into multiple output formats while maintaining the document structure you define. The source code is publicly available on GitHub.
| Template format | Output format |
|---|---|
CSV-structured .tpl | CSV file |
XML-structured .tpl | XML file |
HTML-structured .tpl | HTML or PDF file |
TXT-structured .tpl | TXT file |
Why use Reporter?
Rather than writing complex SQL queries, you reference domains, tables, and fields through intuitive placeholders. This makes report creation faster, more flexible, and easier to maintain.
How it works
Workflow
Reporter follows a simple and efficient workflow that turns your templates into production-ready reports:- Submit templates with optional filters and parameters
- Reporter retrieves data from configured databases (PostgreSQL, MongoDB)
- Template logic is applied (loops, conditions, calculations)
- The final output is generated in the requested format

Architecture
Reporter is built on a layered architecture that keeps responsibilities clear and supports growth:- Data layer: Connects to databases through configured data sources. Supports PostgreSQL and MongoDB, with multi-schema queries for PostgreSQL.
- Business logic layer: Manages template parsing, placeholder resolution, and rendering.
- Storage layer: Stores templates and generated reports using S3-compatible object storage (AWS S3, MinIO, or SeaweedFS).
- Presentation layer: Returns formatted output through RESTful APIs.

What it can do
- Dynamic queries with placeholders: Reference any data point via direct paths — no SQL required.
- Multi-schema support: Query tables across multiple PostgreSQL schemas from a single template using explicit schema syntax.
- Loop and condition logic: Build dynamic content with
forloops,if/elif/elseconditionals, and scoped blocks. - Math operations and aggregation: Perform calculations with
sum_by,avg_by,count_by,min_by,max_by,calc, andaggregate_balance. - Counter tracking: Track and display named counters across template iterations with
counterandcounter_show. - Data transformation filters: Transform values inline with
where,sum,count,replace,slice,strip_zeros, andpercent_of. - Async processing: Heavy reports are handled asynchronously via message queue.
- S3-compatible storage: Templates and reports are stored in any S3-compatible service (AWS S3, MinIO, SeaweedFS).
- Multiple output formats: Generate CSV, XML, HTML, TXT, or PDF output from a single template engine.
Template model
Reporter uses templates that mirror the final document structure. Files must have the
.tpl extension regardless of the content format inside.
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
Database references must be renamed in the project’s
.env file to avoid conflicts when tables share names across databases. Example naming:
midaz_onboarding(PostgreSQL)midaz_onboarding_metadata(MongoDB)
Using placeholders
The placeholder structure follows a path-based syntax:
Multi-schema placeholders
When your PostgreSQL data source has multiple schemas, use the explicit schema syntax to avoid ambiguity:If a table name exists in only one schema, the legacy syntax
{{ base.table.field }} still works — Reporter auto-discovers the correct schema. If a table exists in multiple schemas, Reporter defaults to the public schema. If the table is not in public, Reporter returns an error with suggestions:Building templates
Common blocks
- Loop
- Loop with explicit schema
- Simple condition
- Temporary scope
- Value formatting
Conditional blocks
| Block | Description | Example |
|---|---|---|
| If | Runs block if 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 | {% 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 true | {% if a and b %} |
| Or | Returns true if at least one true | {% if a or b %} |
| Not | Inverts Boolean result | {% if not a %} |
Tags reference
Aggregation tags
sum_by — Sums numeric values from a field across all items in a collection.All aggregation tags use decimal precision to avoid floating-point rounding errors. Missing or non-numeric fields are skipped. Returns
0 if no items match.Date and time tag
date_time — Outputs the current date and time formatted according to the provided format string. Time is generated in UTC.| Code | Meaning | Example |
|---|---|---|
YYYY | 4-digit year | 2025 |
MM | 2-digit month | 01-12 |
dd | 2-digit day | 01-31 |
HH | 2-digit hour (24h) | 00-23 |
mm | 2-digit minute | 00-59 |
ss | 2-digit second | 00-59 |
Arithmetic tag
calc — Evaluates mathematical expressions with support for variables from the template context.| Operator | Description | Precedence |
|---|---|---|
** | Exponentiation | Highest (right-to-left) |
* / | Multiplication, division | Middle |
+ - | Addition, subtraction | Lowest |
( ) | Parentheses | Override precedence |
Variables that cannot be resolved default to
0. Division by zero produces an error.Financial aggregation tag
aggregate_balance — Groups items by a field, selects the most recent entry per account within each group, and sums the balances. Useful for regulatory reports that require the latest balance per account grouped by category.| Field | Type | Description |
|---|---|---|
group_value | string | The value from the group_by field |
balance | decimal | Sum of latest balances per account in the group |
count | integer | Number of accounts in the group |
Maximum collection size: 100,000 items. Results are sorted by
group_value.Counter tags
counter — Increments a named counter by 1. Produces no output. Counters are scoped per render.Filters reference
percent_of
Calculates the percentage of a value relative to a total. Returns a formatted string with 2 decimal places.category.amount = "6.00" and total.expenses = "20.00":
strip_zeros
Removes trailing zeros from a numeric value without rounding.slice
Extracts a substring using start and end indices (0-based).replace
Replaces all occurrences of a search string with a replacement string. Format:"search:replacement".
where
Filters an array of objects by field equality. Supports nested fields via dot notation.sum (filter)
Sums numeric values from a field across all items in an array. Uses decimal precision.count (filter)
Counts elements in an array where a field matches a value. Supports nested fields.contains
Checks if one value is partially included in another. Useful when data includes dynamic prefixes or suffixes.- Source:
0#@external/BRL - Target:
@external/BRL
true because @external/BRL exists within the source value.
Operators and filters summary
| Name | Type | Description |
|---|---|---|
sum_by | Tag | Sum values by field with optional filter |
count_by | Tag | Count items with optional filter |
avg_by | Tag | Calculate average by field |
min_by | Tag | Find minimum value |
max_by | Tag | Find maximum value |
date_time | Tag | Format current date/time |
calc | Tag | Evaluate arithmetic expressions |
aggregate_balance | Tag | Grouped financial balance aggregation |
counter | Tag | Increment a named counter |
counter_show | Tag | Display counter value(s) |
percent_of | Filter | Calculate percentage |
strip_zeros | Filter | Remove trailing zeros |
slice | Filter | Extract substring |
replace | Filter | String replacement |
where | Filter | Filter array by field value |
sum | Filter | Sum array field values |
count | Filter | Count matching items |
contains | Function | Partial string match |
floatformat | Filter | Format decimal places |
Advanced filtering
When generating a report, you can pass filters in the request body to narrow the data. Filters follow a structure of datasource > table > field: Single schema (default):
| Operator | Description | Example |
|---|---|---|
eq | Equal to | { "eq": ["active", "pending"] } |
gt | Greater than | { "gt": [100] } |
gte | Greater than or equal to | { "gte": ["2025-06-01"] } |
lt | Less than | { "lt": [1000] } |
lte | Less than or equal to | { "lte": ["2025-06-30"] } |
between | Value falls within a range | { "between": [100, 1000] } |
in | Value is within a list | { "in": ["active", "pending"] } |
nin | Value is not within a list | { "nin": ["deleted", "archived"] } |
Need inspiration?
Check out the Template examples page to explore what you can do and start shaping your own template.
Authentication and authorization
Reporter does not require authentication by default, but it ships with native Access Manager integration. When enabled, Access Manager provides role-based access control (RBAC) for templates, reports, and data sources — giving you fine-grained control over who can view, create, or manage reporting resources.

