<a id="filters"></a>
<a id="template-filters"></a>
# Filters

A filter changes a value as it is written into a document. Put it after the value with `|`, and give it any values it needs in brackets:

```jinja
{{ form.DraftFormName|upper }}
{{ form.als_fields_set.count() }} field{{ form.als_fields_set.count()|pluralize }}
```

Filters can follow one another, each working on what the one before it gave. To apply a filter to a whole block of a template rather than one value, use `{% filter %}`:

```jinja
{% filter upper %}
This text is written in capitals.
{% endfilter %}
```

This page lists every filter a Document Template can use. Any other filter name stops the template with an error.

The examples use three values a real template would get from its data: `when`, a date and time of 5 March 2026 14:30 UTC; `forms`, three Forms with OIDs DM, AE and VS, of which AE is inactive; and `form`, the first of them. The **Gives** column is exactly what each example writes into the document.

<a id="values-are-escaped"></a>
<a id="template-filters-escaping"></a>
## Values are escaped

Text written into a document has `&`, `<`, `>` and quotes replaced by their escaped forms, so a value cannot break the document's markup. This is why `Fish & Chips` is written as `Fish &amp; Chips`. Filters that produce markup on purpose, such as `markdown` and `clean_html`, mark their result as safe so it is not escaped a second time.

This happens in every type of Template, Text included. A Text Template that writes plain text, Markdown or CSV rather than XML should write its values with `safe`, as in `{{ form.DraftFormName|safe }}`, so that they appear exactly as they are stored.

Two things are not escaped. The text the `{% ai %}` tag writes is used exactly as it comes back, and [The AI tag](ai_tag.html.md#document-template-ai-tag) lists the filters to use on it. The result of a `{% filter %}` block is not escaped again either, but the values inside it already were, so a filter that changes letters can break them: `{% filter upper %}{{ "Fish & Chips" }}{% endfilter %}` writes `FISH &AMP; CHIPS`. Apply such a filter to the value instead: `{{ "Fish & Chips"|upper }}`.

<a id="filters-added-by-appname"></a>
## Filters added by TrialGrid

| Filter | What it does | Example | Gives |
| --- | --- | --- | --- |
| `chunk` | Splits a list into lists of the length you give. The last one holds what is left. | `{% for row in [1, 2, 3, 4, 5]\|chunk(2) %}[{{ row\|join(" ") }}] {% endfor %}` | `[1 2] [3 4] [5]` |
| `clean_html` | Keeps only the HTML tags you list and removes the others, keeping their text. With no list, keeps a standard set of text formatting tags. On a Word, Excel or PDF Template, follow it with `convert_html`. | `{{ "<p>Hello <span>there</span></p>"\|clean_html(["p"]) }}` | `<p>Hello there</p>` |
| `convert_html` | Turns HTML into Word or Excel formatting, so bold, lists and paragraphs appear as they would on a web page. Only in Word and Excel Templates. |  |  |
| `date` | Writes a date and time as year, month and day. Gives nothing for an empty value. | `{{ when\|date }}` | `2026-03-05` |
| `format_datetime` | Writes a date and time in the format you give, using the codes in [Python Date Formats](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes). Gives nothing for an empty value. | `{{ when\|format_datetime("%d %b %Y %H:%M") }}` | `05 Mar 2026 14:30` |
| `html_sanitize` | Removes HTML tags and attributes that are not on a fixed list of safe formatting, keeping their text. | `{{ "<b>Bold</b><script>x()</script>"\|html_sanitize }}` | `<b>Bold</b>x()` |
| `iso` | Writes a date and time in the ISO 8601 format. Gives nothing for an empty value. | `{{ when\|iso }}` | `2026-03-05T14:30:00+00:00` |
| `line_breaks_br` | Escapes the text, then turns each line break into `<br/>`. On a Word or Excel Template, follow it with `convert_html`. | `{{ "one\ntwo"\|line_breaks_br }}` | `one<br/>two` |
| `markdown` | Turns Markdown into HTML. Any HTML already in the text is removed first. On a Word or Excel Template, follow it with `convert_html`. | `{{ "Some **bold** text"\|markdown }}` | `<p>Some <strong>bold</strong> text</p>` |
| `normalize_decimal` | Writes a decimal number without trailing zeros or scientific notation. Any other value is written as it is. | `{{ 2.50\|normalize_decimal }}` | `2.5` |
| `pluralize` | Applied to a number, gives `s` unless the number is 1, for adding to a word. For other endings, give the singular and plural ending separated by a comma. | `3 Form{{ 3\|pluralize }}, 1 Form{{ 1\|pluralize }}, 2 Entr{{ 2\|pluralize("y,ies") }}` | `3 Forms, 1 Form, 2 Entries` |
| `remove_line_break` | Joins the lines of a text into one line, with a space between each. HTML in the text is escaped, so it is shown rather than used. | `{{ "one\ntwo"\|remove_line_break }}` | `one two` |
| `rml_sanitize` | Turns HTML into the markup a PDF Template's paragraphs accept. Links are removed and their text kept, unless you pass `True`. | `{{ "<p>See <a href='https://example.com'>this</a></p>"\|rml_sanitize }}` | `See this` |
| `safe_json` | Marks JSON text as safe to place inside a `<script>` block, replacing `<`, `>` and `&` with their JSON escapes. | `{{ '{"a": "<b>"}'\|safe_json }}` | `{"a": "\u003cb\u003e"}` |
| `slugify` | Turns text into lowercase letters, numbers and hyphens, suitable for a file name or an anchor. | `{{ "Adverse Events (AE)"\|slugify }}` | `adverse-events-ae` |
| `strip_tags` | Removes HTML tags, leaving their text. | `{{ "<p>Hello <b>there</b></p>"\|strip_tags }}` | `Hello there` |
| `timezone` | Converts a date and time to the time zone you name. See [List of Time Zones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). | `{{ when\|timezone("America/New_York")\|format_datetime("%H:%M") }}` | `09:30` |
| `utc` | Writes a date and time with the name of its time zone. It does not convert the time: use `timezone` first for that. | `{{ when\|utc }}` | `2026-03-05 14:30:00 UTC` |
| `xml_escape` | Escapes `&`, `<`, `>` and quotes, so text cannot break the markup of a PDF, Word or Excel Template. | `{{ "Fish & Chips <hot>"\|xml_escape }}` | `Fish &amp; Chips &lt;hot&gt;` |

`convert_html` has no example because what it gives is Word or Excel markup. See [Generating Word Documents](generating_word_documents.html.md#generating-word-documents) and [Generating Excel Documents](generating_excel_documents.html.md#generating-excel-documents).

<a id="filters-from-jinja"></a>
## Filters from Jinja

These come with Jinja, the template language Document Templates are written in. The [Jinja documentation](https://jinja.palletsprojects.com/en/3.1.x/templates/#list-of-builtin-filters) describes each one in more depth.

| Filter | What it does | Example | Gives |
| --- | --- | --- | --- |
| `abs` | The number without its sign. | `{{ -3\|abs }}` | `3` |
| `attr` | An attribute of an object, by name. | `{{ form\|attr("OID") }}` | `DM` |
| `batch` | Splits a list into lists of the length you give. A second value fills out the last one. | `{% for row in [1, 2, 3]\|batch(2, 0) %}[{{ row\|join(" ") }}] {% endfor %}` | `[1 2] [3 0]` |
| `capitalize` | The first letter upper case and the rest lower case. | `{{ "adverse EVENTS"\|capitalize }}` | `Adverse events` |
| `center` | The text centred in the width you give. | `[{{ "AE"\|center(6) }}]` | `[ AE ]` |
| `count` | Another name for `length`. | `{{ [1, 2, 3]\|count }}` | `3` |
| `d` | Another name for `default`. | `{{ missing\|d("none") }}` | `none` |
| `default` | The value you give if the value is undefined. Pass `true` as a second value to use it for empty values as well. | `{{ ""\|default("none", true) }}` | `none` |
| `dictsort` | The items of a dictionary sorted by key, or by value with `by="value"`. | `{% for k, v in {"b": 2, "a": 1}\|dictsort %}{{ k }}={{ v }} {% endfor %}` | `a=1 b=2` |
| `e` | Another name for `escape`. | `{{ "<b>"\|e }}` | `&lt;b&gt;` |
| `escape` | Escapes `&`, `<`, `>` and quotes for HTML. | `{{ "Fish & Chips"\|escape }}` | `Fish &amp; Chips` |
| `filesizeformat` | A number of bytes as a readable size. | `{{ 1500000\|filesizeformat }}` | `1.5 MB` |
| `first` | The first item. | `{{ ["DM", "AE"]\|first }}` | `DM` |
| `float` | The value as a decimal number. | `{{ "2.5"\|float + 1 }}` | `3.5` |
| `forceescape` | Escapes for HTML even a value already marked safe. | `{{ "<b>"\|forceescape }}` | `&lt;b&gt;` |
| `format` | Fills a printf-style pattern with the values you give. | `{{ "%s has %d fields"\|format("DM", 3) }}` | `DM has 3 fields` |
| `groupby` | Groups a list of objects by an attribute. Each group has a `grouper` and its `list`. | `{% for g in forms\|groupby("DraftFormActive") %}{{ g.grouper }}: {{ g.list\|map(attribute="OID")\|join(", ") }}; {% endfor %}` | `False: AE; True: DM, VS;` |
| `indent` | Indents every line but the first by the number of spaces you give. The example shows its line break as `/`. | `{{ "a\nb"\|indent(2)\|replace("\n", "/") }}` | `a/ b` |
| `int` | The value as a whole number. | `{{ "42"\|int + 1 }}` | `43` |
| `items` | The key and value pairs of a dictionary, for a loop. | `{% for k, v in {"a": 1}\|items %}{{ k }}={{ v }}{% endfor %}` | `a=1` |
| `join` | The items of a list joined with the text you give. `attribute=` joins one attribute of each. | `{{ ["DM", "AE"]\|join(", ") }}` | `DM, AE` |
| `last` | The last item. | `{{ ["DM", "AE"]\|last }}` | `AE` |
| `length` | How many items, or how many characters in a text. On a set of objects, use `.count()` instead, which does not read them. | `{{ ["DM", "AE"]\|length }}` | `2` |
| `list` | The value as a list. | `{{ "abc"\|list\|join("-") }}` | `a-b-c` |
| `lower` | The text in lower case. | `{{ "DM"\|lower }}` | `dm` |
| `map` | One attribute of each object (`attribute=`), or a filter applied to each item. | `{{ forms\|map(attribute="OID")\|join(", ") }}` | `DM, AE, VS` |
| `max` | The largest item. `attribute=` compares one attribute of each. | `{{ [3, 7, 5]\|max }}` | `7` |
| `min` | The smallest item. `attribute=` compares one attribute of each. | `{{ [3, 7, 5]\|min }}` | `3` |
| `pprint` | The value laid out for reading, for checking what a value holds. | `{{ [1, 2]\|pprint }}` | `[1, 2]` |
| `random` | An item chosen at random. |  |  |
| `reject` | The items that fail a test. | `{{ [1, 2, 3, 4]\|reject("odd")\|join(", ") }}` | `2, 4` |
| `rejectattr` | The objects whose attribute fails a test, or is false if you name no test. | `{{ forms\|rejectattr("DraftFormActive")\|map(attribute="OID")\|join(", ") }}` | `AE` |
| `replace` | The text with every occurrence of one piece replaced by another. | `{{ "AE_FORM"\|replace("_", " ") }}` | `AE FORM` |
| `reverse` | The items, or the characters of a text, in the opposite order. | `{{ ["DM", "AE"]\|reverse\|join(", ") }}` | `AE, DM` |
| `round` | The number rounded to the places you give. `method='floor'` or `method='ceil'` rounds down or up. | `{{ 2.567\|round(1) }}` | `2.6` |
| `safe` | Marks a value as safe, so it is not escaped. Use it on a value you know cannot break the document's markup, or in a Text Template whose output is not markup. | `{{ "<b>bold</b>"\|safe }}` | `<b>bold</b>` |
| `select` | The items that pass a test. | `{{ [1, 2, 3, 4]\|select("odd")\|join(", ") }}` | `1, 3` |
| `selectattr` | The objects whose attribute passes a test, or is true if you name no test. | `{{ forms\|selectattr("DraftFormActive")\|map(attribute="OID")\|join(", ") }}` | `DM, VS` |
| `slice` | Splits a list into the number of columns you give. | `{% for column in [1, 2, 3, 4, 5]\|slice(2) %}[{{ column\|join(" ") }}] {% endfor %}` | `[1 2 3] [4 5]` |
| `sort` | The items in order. `reverse=true` for largest first, `attribute=` to sort objects by one of their attributes. | `{{ [3, 1, 2]\|sort(reverse=true)\|join(", ") }}` | `3, 2, 1` |
| `string` | The value as text. | `{{ (1)\|string + "st" }}` | `1st` |
| `striptags` | Removes HTML tags and runs of spaces. | `{{ "<p>Hello <b>there</b></p>"\|striptags }}` | `Hello there` |
| `sum` | The items added up. `attribute=` adds one attribute of each. | `{{ [1, 2, 3]\|sum }}` | `6` |
| `title` | Each word starting with a capital letter. | `{{ "adverse events"\|title }}` | `Adverse Events` |
| `tojson` | The value as JSON, safe to place inside a `<script>` block. | `{{ {"a": [1, 2]}\|tojson }}` | `{"a": [1, 2]}` |
| `trim` | The text without spaces at the start and end. | `[{{ " AE "\|trim }}]` | `[AE]` |
| `truncate` | The text cut to the length you give, ending in `...`. | `{{ "Adverse Events Log"\|truncate(12, killwords=true) }}` | `Adverse E...` |
| `unique` | The items without duplicates. | `{{ ["CheckBox", "Text", "CheckBox"]\|unique\|join(", ") }}` | `CheckBox, Text` |
| `upper` | The text in upper case. | `{{ "dm"\|upper }}` | `DM` |
| `urlencode` | The text encoded for use in a web address. | `{{ "a b&c"\|urlencode }}` | `a%20b%26c` |
| `urlize` | Turns web addresses in a text into HTML links. On a Word or Excel Template, follow it with `convert_html`. | `{{ "See https://example.com"\|urlize }}` | `See <a href="https://example.com" rel="noopener">https://example.com</a>` |
| `wordcount` | How many words in a text. | `{{ "Adverse Events Log"\|wordcount }}` | `3` |
| `wordwrap` | Wraps a text at the width you give, with a line break or the `wrapstring` you give. | `{{ "Adverse Events Log"\|wordwrap(10, wrapstring=" / ") }}` | `Adverse / Events Log` |
| `xmlattr` | Turns a dictionary into HTML or XML attributes. | `<td{{ {"class": "oid"}\|xmlattr }}>` | `<td class="oid">` |

`random` has no example because what it gives changes every time.
