> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lerian.studio/llms.txt
> Use this file to discover all available pages before exploring further.

# Reporter template engine

> How Reporter evaluates a template: the field map it derives, the data context it builds, blocks, template filters, row filters, and the multi-schema data source model.

A Reporter template is a plain-text `.tpl` file. Reporter renders it with a Pongo2 engine, which reads Django-style tags and pipe filters.

[Template reference](/en/reporter/template-reference) lists every tag and filter. [Template formats](/en/reporter/template-examples) shows a worked file per output format. This page covers what the engine does around that syntax: what it reads out of your file, what data it puts in front of it, and which limits it applies.

## The field map

***

When you upload a template, Reporter analyses the text and derives a **field map**: every data source, every table, and every field the template names.

```json theme={null}
{
  "external_db": {
    "sales__orders": ["id", "total", "created_at"]
  }
}
```

The map is stored with the template metadata and travels with each report request. Extraction reads only what the map lists. A field your template never names is never queried, so a template stays cheap as the underlying tables grow.

Two consequences follow. First, Reporter derives the map when you upload or replace the file, never at render time. A new field reference reaches a report only after you upload the changed template.

Second, Reporter checks the map against the live schema of each data source at upload time. A wrong table name or a wrong field name surfaces there, before any report runs. When a data source cannot answer, Reporter returns warnings and still accepts the template. One unreachable database does not block your work.

## The data context

***

Extraction builds one context for the render. The first level is the data source config name. The second level is the table. Each value is a list of rows.

```json theme={null}
{
  "external_db": {
    "orders": [
      { "id": "018f...", "total": "1200.00", "created_at": "2026-07-01" }
    ]
  }
}
```

Your template addresses that context by the same names it used to declare them:

```django theme={null}
{% for order in external_db.orders %}
  {{ order.id }} — {{ order.total|floatformat:2 }}
{% endfor %}
```

Rows come back under the key your template wrote. A bare table name stays bare. A schema-qualified reference becomes `schema__table`, with a double underscore, in both the field map and the render context. Row filters are looser and accept either `schema.table` or `schema__table`.

### Variables

A template reads the context through variables. A loop alias binds one row at a time, and `{% with %}` names part of the context for the block below it. Both aliases are local to the block that declares them.

The analyser follows an alias back to the table behind it. A field you read as `order.total` inside a loop over `external_db.orders` lands in the field map as `total` under that table, so extraction returns it.

## Multi-schema data sources

***

An operator declares each data source through environment variables. `CONFIG_NAME` sets the name your templates use, and `SCHEMAS` lists the schemas Reporter discovers on it. Keep the environment-name segment and the `CONFIG_NAME` value identical, as in the block below:

```bash theme={null}
DATASOURCE_EXTERNAL_DB_CONFIG_NAME=external_db
DATASOURCE_EXTERNAL_DB_HOST=external-postgres
DATASOURCE_EXTERNAL_DB_PORT=5432
DATASOURCE_EXTERNAL_DB_USER=db_user
DATASOURCE_EXTERNAL_DB_DATABASE=external_database
DATASOURCE_EXTERNAL_DB_TYPE=postgresql
DATASOURCE_EXTERNAL_DB_SCHEMAS=sales,inventory,reporting
```

Without `SCHEMAS`, Reporter discovers the `public` schema alone. See [Environment variables](/en/reporter/reporter-environment-variables) for the full block.

The engine resolves a bare table name against every discovered schema:

| Owners of the table name               | Result                                                              |
| -------------------------------------- | ------------------------------------------------------------------- |
| Exactly one schema                     | The engine reads that table.                                        |
| Several schemas, one of them `public`  | The engine reads the `public` table.                                |
| Several schemas, none of them `public` | The engine reports the table as ambiguous and names the candidates. |
| No schema                              | The engine reports the table as not found.                          |

Qualify the reference to remove the ambiguity. The qualified form names the source, the schema, and the table:

```django theme={null}
{% for order in external_db:sales.orders %}
  {{ order.id }}
{% endfor %}
```

A qualified reference must match the discovered schema exactly. The engine does not search other schemas for it.

## Blocks

***

A block is the unit the visual template builder works in. Thirteen types exist, in six categories:

| Category              | Block types                                             |
| --------------------- | ------------------------------------------------------- |
| Basic (`basic`)       | `text`, `variable`, `comment`                           |
| Control (`control`)   | `loop`, `conditional`, `with`                           |
| Data (`data`)         | `aggregation`, `calculation`, `date_time`, `expression` |
| Layout (`layout`)     | `section`                                               |
| Numbering (`dimp`)    | `counter`                                               |
| Advanced (`advanced`) | `custom_tag`                                            |

The value in brackets is the category string the block catalogue returns, so a client matching on the API response keys on it rather than on the label.

Four of them hold children: `loop`, `conditional`, `section`, and `with`. A conditional also carries alternative branches. Blocks nest up to fifty levels deep.

Blocks are never stored. The builder sends them to Reporter, and Reporter returns Pongo2 source plus the field map that source implies. The template you upload is always the text.

Three operations support that flow:

* [Validate template blocks](/en/reference/reporter/validate-template-blocks) checks the structure, parses the generated source, and reports each problem against the block that caused it.
* [Generate template code](/en/reference/reporter/generate-template-code) returns the finished source for an output format.
* [List block definitions](/en/reference/reporter/list-block-definitions) returns the catalogue, so a client stays in step with the engine.

## Two kinds of filter

***

Reporter uses the word filter for two different things, and they run at different times.

**Row filters** run during extraction. They live in the report request, not in the template, and they narrow the rows the database returns. The payload nests them three levels deep: data source, then table, then field.

```json theme={null}
{
  "templateId": "018f2a6c-1c2f-7a10-9f1e-2b8c4d5e6f70",
  "filters": {
    "external_db": {
      "orders": {
        "status": { "nin": ["cancelled", "draft"] },
        "created_at": { "gte": ["2026-07-01"], "lte": ["2026-07-31"] }
      }
    }
  }
}
```

Eight operators exist:

| Operator  | Meaning                     | Values      |
| --------- | --------------------------- | ----------- |
| `eq`      | Matches any listed value    | One or more |
| `gt`      | Above the value             | One         |
| `gte`     | At or above the value       | One         |
| `lt`      | Below the value             | One         |
| `lte`     | At or below the value       | One         |
| `between` | Inside an inclusive range   | Exactly two |
| `in`      | Matches any listed value    | One or more |
| `nin`     | Excludes every listed value | One or more |

Every operator takes an array. Several operators on one field combine, as the date range above shows.

**Template filters** run during the render, after the rows arrive. They shape a value inside the document, and they use pipe syntax: `{{ value|percent_of:total }}`. [List filter definitions](/en/reference/reporter/list-filter-definitions) returns the catalogue with an example per filter.

## The render step

***

The engine parses the prepared template once per report and executes it against the context. Two behaviours of that step change how you design a document.

Numeric output loses its trailing zeros, so `1200.00` renders as `1200`. Use `floatformat` when a column needs fixed decimals. Values that carry several dots stay intact, which keeps an accounting code such as `1.1.2.00.000` unchanged.

An XML template can declare the encoding of the stored file. Put the marker on the same line as the XML declaration:

```django theme={null}
{# reporter:output-encoding=utf-16be #}<?xml version="1.0" encoding="UTF-16"?>
```

Reporter then writes the file in UTF-16BE with no byte order mark. PDF output ignores the marker.

## Limits the engine applies

***

<Note>
  Reporter blocks the Pongo2 tags that load or extend another file: `include`, `extends`, `import`, `block`, and `ssi`. A template is a self-contained document.
</Note>

Block nesting stops at fifty levels, on validation and on code generation alike. Free-form block fields reject template delimiters, so builder input cannot inject a tag. Uploaded templates reject script tags.

## Next steps

***

<CardGroup cols={2}>
  <Card title="How report generation works" icon="diagram-project" href="/en/reporter/how-report-generation-works">
    The path from a report request to a file you can download.
  </Card>

  <Card title="Template reference" icon="code" href="/en/reporter/template-reference">
    Every tag, filter, and operator the syntax accepts.
  </Card>

  <Card title="Template formats" icon="file-lines" href="/en/reporter/template-examples">
    A worked template per output format.
  </Card>

  <Card title="Reporter core concepts" icon="cubes" href="/en/reporter/reporter-core-concepts">
    Templates, data sources, reports, and deadlines in one place.
  </Card>
</CardGroup>
