---
title: "Jinja reference"
slug: "jinja-reference"
updated: 2026-06-16T18:17:49Z
published: 2026-06-16T18:17:49Z
canonical: "help.quickbase.com/jinja-reference"
---

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

# Jinja reference

> [!NOTE]
> **Note:** This page is a reference. Use the table of contents or **Ctrl+F** to find what you need.

## Quick syntax cheat sheet

| Shape | What it does | Example |
| --- | --- | --- |
| `{{ value }}` | Show a value | `{{a.first_name}}` |
| `{% statement %}` | Do something (if, for, set…) | `{% if a.amount &gt; 100 %}` |
| `{# comment #}` | A note that doesn't appear in the output | `{# remember to update this #}` |
| `\|` | Apply a filter to a value | `{{a.name \| upper}}` |
| `~` | Stitch two strings together | `{{a.first ~ '-' ~ a.last}}` |
| `.` | Reach a field on an object | `{{a.first_name}}` |
| `['…']` | Reach a field whose name has spaces | `{{a['Date Created']}}` |
| `[0]` | Reach the first item in a list | `{{b[0].name}}` |

## About Jinja in Pipelines

Pipelines runs Jinja version 2.11.3 in a limited environment. You can't import Python modules, read files from disk, or call out to the network from inside a Jinja expression. Everything you can do is on this page.

Jinja isn't the Quickbase formula language. The two share goals (turn data into something useful) but have different syntax. Many filters below have a close equivalent in formulas, called out in the **Quickbase formula** column. Treat the mapping as a loose match, not exact.

## Variables—dot, bracket, get, set

3 ways to reach a field on a step:

| Way to write it | When to use |
| --- | --- |
| `{{a.field_name}}` | Standard: name is one word, no spaces or special characters |
| `{{a['Field Name']}}` | The field name has spaces, or matches a reserved word |
| `{{a.get('field_name', 'fallback')}}` | The field might not exist; supply a default |

Create your own variable:

```plaintext
{% set total = 0 %}
{% set greeting = 'Hello, ' ~ a.first_name %}
```

For a variable you want to update inside a loop, use `namespace()`. Learn more in [Pipelines-specific extras](/v1/docs/jinja-reference#pipelines-specific-extras).

## Statements, expressions, comments

```plaintext
{{ value }}       # show a value
{% statement %}   # do something — if, for, set
{# comment #}     # leave a note that won't appear in the output
```

Comments are useful for temporarily disabling a block of Jinja while you debug.

## If / elif / else

```plaintext
{% if a.amount > 1000 %}Big
{% elif a.amount > 100 %}Medium
{% else %}Small
{% endif %}
```

- `{% if … %}`—the first question
- `{% elif … %}`—(optional, can have many) another question if the previous was no
- `{% else %}`—(optional) what to do if nothing matched
- `{% endif %}`—closes the block

To learn more, refer to [Start with Jinja in Pipelines](/v1/docs/start-with-jinja-in-pipelines).

## For loops and loop helpers

```plaintext
{% for r in b %}
{{r.name}}
{% endfor %}
```

Inside any loop, these helpers are available:

| Helper | What it gives you |
| --- | --- |
| `loop.index` | Position of the current item, starting at 1 |
| `loop.index0` | Position of the current item, starting at 0 |
| `loop.first` | `true` on the first item only |
| `loop.last` | `true` on the last item only |
| `loop.length` | Total number of items |
| `loop.revindex` | Position counted from the end, starting at 1 |
| `loop.previtem` | The previous item (not available on the first) |
| `loop.nextitem` | The next item (not available on the last) |

To learn more, refer to [Start with Jinja in Pipelines](/v1/docs/start-with-jinja-in-pipelines).

## Whitespace control

Jinja keeps the whitespace around your statements by default, which can break JSON payloads or leave empty lines. Add a hyphen to either side to strip whitespace:

```plaintext
{%- if a.amount > 100 -%}
```

- `{%-` strips whitespace before the tag
- `-%}` strips whitespace after the tag

The same works for expressions: `{{- value -}}`.

## Filters by category

A filter changes a value, applied with the pipe character `|`. Filters chain: `{{a.notes | striptags | trim | upper}}` strips HTML, trims whitespace, then uppercases, in that order.

### Date/time

| Filter | What it does | Example | Quickbase formula |
| --- | --- | --- | --- |
| `time.now` | Current date and time, in UTC | `{{time.now}}` | `Now()` |
| `time.today` | Today's date at 00:00 UTC | `{{time.today}}` | `Today()` |
| `time.delta(…)` | Add or subtract a span of time. Plural arg (`days=7`) adds; singular arg (`day=15`) sets that part absolutely | `{{time.today + time.delta(days=7)}}` | `+ ToDuration()` |
| `time.parse(string)` | Turn a date-shaped string into a real date | `{{time.parse('2025-01-15').strftime('%B %d')}}` | `ToDate()` |
| `timezone(code)` | Convert UTC to a specific timezone. Codes from the [tz database](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) | `{{time.now \| timezone('America/New_York')}}` | — |
| `date_ymd(separator?)` | Format as year-month-day. Default separator is `-` | `{{time.now \| date_ymd}}` → `2025-01-15` | `ToFormattedText()` |
| `date_mdy(separator?)` | Format as month-day-year | `{{time.now \| date_mdy('/')}}` → `01/15/2025` | `ToFormattedText()` |
| `date_dmy(separator?)` | Format as day-month-year | `{{time.now \| date_dmy('.')}}` → `15.01.2025` | `ToFormattedText()` |
| `.strftime(format)` | Custom date format. Method, not filter. Use dot notation, not pipe | `{{time.now.strftime('%a, %B %d')}}` | `ToFormattedText()` |
| `.date()` | Drop the time portion off a date/time | `{{a.created_at.date()}}` | `ToDate()` |
| `.days` | When you subtract two dates, get the number of days as a number | `{{(a.end - a.start).days}}` | `ToDays()` |
| `.seconds` | When you subtract two dates, get the number of seconds | `{{(a.end - a.start).seconds}}` | — |
| `timestamp_to_time(value)` | Convert a Unix timestamp (seconds since 1970) to UTC | `{{1608112432 \| timestamp_to_time}}` → `12-16-2020 01:53 AM` | — |

> [!NOTE]
> **Note:** `.strftime` and `.find` are methods, not filters. Use `value.strftime(...)`, not `value | strftime(...)`. The pipe form silently does the wrong thing.

Common `strftime` format codes:

| Code | Result |
| --- | --- |
| `%Y` | Year, 4 digits (2025) |
| `%m` | Month, 2 digits (01–12) |
| `%d` | Day, 2 digits (01–31) |
| `%B` | Month name (January) |
| `%b` | Month abbreviated (Jan) |
| `%A` | Weekday name (Monday) |
| `%a` | Weekday abbreviated (Mon) |
| `%H` | Hour, 24-hour (00–23) |
| `%I` | Hour, 12-hour (01–12) |
| `%M` | Minute (00–59) |
| `%S` | Second (00–59) |
| `%p` | AM or PM |

### Text

| Filter | What it does | Example | Quickbase formula |
| --- | --- | --- | --- |
| `upper` | Make all uppercase | `{{a.name \| upper}}` | `ToUpper()` |
| `lower` | Make all lowercase | `{{a.name \| lower}}` | `ToLower()` |
| `title` | Capitalize the first letter of each word | `{{a.name \| title}}` | — |
| `capitalize` | Capitalize the first letter, lowercase the rest | `{{a.name \| capitalize}}` | — |
| `trim` | Remove whitespace from the ends | `{{a.name \| trim}}` | `Trim()` |
| `replace(old, new)` | Replace a substring | `{{a.value \| replace('$', '')}}` | `SearchAndReplace()` |
| `length` | Number of characters | `{{a.name \| length}}` | `Length()` |
| `wordcount` | Number of words | `{{a.notes \| wordcount}}` | — |
| `slice(start, end)` | Take a portion of a string. Use `value[start:end]` syntax | `{{a.code[2:6]}}` | `Mid()` |
| `~` | Stitch two strings together | `{{a.first ~ ' ' ~ a.last}}` | `&amp;` |
| `.split(delimiter)` | Split a string into a list by a delimiter (method) | `{{a.email.split('@')[1]}}` — gets the domain | `Part()` |
| `.find(substring)` | Position of a substring (-1 if not found). Method, not filter | `{{a.text.find('hello')}}` | — |
| `striptags` | Remove all HTML/XML tags | `{{a.html_field \| striptags}}` | — |
| `text` | Strip HTML and formatting from a rich text field | `{{a.notes \| text}}` | — |
| `html2text` | Convert HTML to Markdown | `{{a.body \| html2text}}` | — |
| `escape` | Convert `&lt;`, `&gt;`, `&amp;`, quotes to HTML-safe entities | `{{a.name \| escape}}` | — |
| `urlencode` | Make a string safe to use in a URL | `{{a.search \| urlencode}}` | — |
| `format(args)` | Insert values into a template string | `{{ "Hello, %s" \| format(a.name) }}` | — |
| `tojson` / `to_json` | Convert to JSON-safe text. Use when building JSON payloads, as it handles escaping automatically | `"name": {{a.name \| tojson}}` | — |
| `safe` | Mark a value as already safe; skip escaping | `{{a.html \| safe}}` | — |
| `truncate(length)` | Shorten a string and add `...` | `{{a.notes \| truncate(50)}}` | — |

### Numbers

| Filter | What it does | Example | Quickbase formula |
| --- | --- | --- | --- |
| `int` | Convert to a whole number | `{{a.text \| int}}` | `ToNumber()` |
| `float` | Convert to a decimal number | `{{a.text \| float}}` | `ToNumber()` |
| `round(precision)` | Round to a number of decimals | `{{a.amount \| round(2)}}` | `Round()` |
| `abs` | Absolute value (drop the minus sign) | `{{a.delta \| abs}}` | `Abs()` |
| `max` | Largest value in a list | `{{[1,2,3] \| max}}` | `Max()` |
| `min` | Smallest value in a list | `{{[1,2,3] \| min}}` | `Min()` |

### Lists and aggregation

These work on lists—usually a search step's output, or a multi-select or List-User field.

| Filter | What it does | Example |
| --- | --- | --- |
| `length` / `count` | Number of items | `{{b \| length}}` |
| `first` | First item | `{{b \| first}}` |
| `last` | Last item | `{{b \| last}}` |
| `sort` | Sort the list. Add `attribute='field'` for lists of records | `{{b \| sort(attribute='name')}}` |
| `unique` | Remove duplicates | `{{b \| unique}}` |
| `join(separator)` | Stitch list items into one string | `{{['a','b','c'] \| join(',')}}` → `a,b,c` |
| `sum` | Sum a list of numbers. Add `attribute='field'` for records. Fails on null values; use `namespace()` for null safety | `{{b \| sum(attribute='amount')}}` |
| `map(attribute='x')` | Pull one field out of each record | `{{b \| map(attribute='email') \| list}}` |
| `select(test)` | Keep only items that pass a test | `{{[1,2,3,4] \| select('odd') \| list}}` |
| `reject(test)` | Drop items that pass a test | `{{[1,2,3,4] \| reject('odd') \| list}}` |
| `selectattr(attr, test, value)` | Keep records whose field passes a test | `{{b \| selectattr('status', 'equalto', 'Open') \| list}}` |
| `rejectattr(attr, test, value)` | Drop records whose field passes a test | `{{b \| rejectattr('status', 'equalto', 'Closed') \| list}}` |
| `list` | Force the result into a list. Often needed after `map`, `select`, and similar filters | `{{b \| map(attribute='id') \| list}}` |
| `range(start, end)` | Generate a list of numbers from `start` to `end-1` | `{{range(1, 6) \| list}}` → `[1,2,3,4,5]` |
| `random` | Pick a random item | `{{['a','b','c'] \| random}}` |

### Encoding and hashing

| Filter | What it does |
| --- | --- |
| `base64_encode` / `base64_decode` | Encode or decode a string in Base64. Used for embedding files in JSON payloads |
| `md5` | MD5 hash |
| `sha1` / `sha224` / `sha256` / `sha384` / `sha512` | SHA hash family |
| `hmac(key, algorithm)` | Hash-based message authentication code, used for signing API requests |

### Other

| Filter | What it does | Example | Quickbase formula |
| --- | --- | --- | --- |
| `default(value)` | Use a fallback if input is undefined or empty | `{{a.note \| default('No notes')}}` | `Nz()` |
| `string` | Force a value into a string | `{{a.value \| string}}` | `ToText()` |

## Tests

A test asks a yes/no question about a value. Tests use the word `is`, not the pipe character.

```plaintext
{% if a.field is defined %}...{% endif %}
{% if a.field is none %}...{% endif %}
{% if a.amount is number %}...{% endif %}
```

| Test | True when… |
| --- | --- |
| `defined` | The variable exists |
| `none` | The value is empty / null |
| `string` | The value is text |
| `number` | The value is a number |
| `integer` | The value is a whole number |
| `float` | The value is a decimal number |
| `boolean` | The value is `true` or `false` |
| `iterable` | The value can be looped through (a list, for example) |
| `mapping` | The value is a key-value object (like a JSON object) |
| `sequence` | The value is an ordered collection (a list or a string) |
| `odd` | The number is odd |
| `even` | The number is even |
| `divisibleby(n)` | The number divides evenly by `n` |
| `equalto` / `eq` | Equals a given value |
| `ne` | Not equal to |
| `lt` / `le` / `gt` / `ge` | Less / less-or-equal / greater / greater-or-equal |
| `in` | The value is in a given list or string |

Combine with `not` to invert: `{% if a.field is not none %}`.

## Pipelines-specific extras

These features are added by Pipelines on top of standard Jinja.

### The runtime object

Every pipeline run has a `runtime` object with metadata about itself.

| Property | What it gives you |
| --- | --- |
| `runtime.pipeline_name` | User-defined pipeline name |
| `runtime.pipeline_id` | Numeric pipeline ID |
| `runtime.pipeline_url` | Link to the pipeline editor |
| `runtime.run_id` | Unique ID of this run |
| `runtime.activity_url` | Direct link to this run in the activity log |
| `runtime.triggered_at` | UTC timestamp of when the run started |
| `runtime.user_id` | Pipelines user ID of whoever triggered the run |

Example—build a clickable link into an error alert:

```plaintext
Pipeline "{{runtime.pipeline_name}}" failed.
View activity: {{runtime.activity_url}}
```

### The trigger context object (Quickbase triggers)

On a Quickbase trigger step, `a.context` gives you details about the app and table that fired the run.

| Property | What it gives you |
| --- | --- |
| `a.context.app_name` | The app name |
| `a.context.app_id` | The app ID |
| `a.context.table_name` | The table name |
| `a.context.table_id` | The table ID |
| `a.context.realm_host` | Your Quickbase realm host |
| `a.context.realm_id` | Numeric realm ID |
| `a.context.occurred_at` | When the change happened, UTC |
| `a.context.user` | The user who triggered the change |

### `$prev`

Access the previous value of a field when the trigger was a Record Updated event. Goes between the step letter and the field name:

```plaintext
{% if a.status != a.$prev.status %}...{% endif %}
```

Only available on update triggers. Does not work in Quickbase Advanced Query filter strings. See [Start with Jinja in Pipelines](/docs/jinja-reference#) for the full explanation.

### `{{CLEAR}}`

Explicitly empty a field in a write step. By default, Pipelines silently skips empty values to prevent accidental data loss. Use `{{CLEAR}}` to override that.

```plaintext
{% if a.priority == 'No priority' %}{{CLEAR}}{% else %}{{a.priority}}{% endif %}
```

### `raise_error(message)`

Stop a pipeline run with a custom error message. Useful for input validation.

```plaintext
{% if a.amount > 10000 %}{{raise_error('Amount over the limit')}}{% endif %}
```

### `joiner()` and `namespace()`

Two helpers for situations where standard Jinja has limitations.

`joiner(separator)` builds a comma-separated list inside a loop without a leading or trailing separator. The first call returns nothing; every subsequent call returns the separator.

```plaintext
{% set comma = joiner(', ') %}
{% for c in b %}{{comma()}}{{c.name}}{% endfor %}
```

Produces *Alice, Bob, Carol* with no leading or trailing comma.

`namespace(...)` is a container for variables that need to change inside a loop. A normal `{% set %}` inside a loop only updates for that iteration; `namespace` persists the value across iterations.

Most common use: a running total that ignores nulls.

```plaintext
{% set total = namespace(value=0) %}
{% for r in b %}
  {% set total.value = total.value + (r.amount | default(0, true)) %}
{% endfor %}
Total: {{total.value}}
```

## Advanced Query—Quickbase query syntax

> [!WARNING]
> Warning: Advanced Query / filter fields on Quickbase search and trigger steps use **Quickbase query syntax, not Jinja**. When you select code mode on one of these fields, you're typing query syntax. Jinja can still be embedded inside the value portion—see [Embed Jinja inside a query](/v1/docs/jinja-reference#embed-jinja-inside-a-query) below.

### Query format

Each query is wrapped in curly braces and has three parts separated by periods:

```plaintext
{fid.OPERATOR.'matching_value'}
```

- **fid**—the field's numeric ID
- **OPERATOR**—uppercase, from the table below
- **matching_value**—what to compare against, in single quotes

Example—find records where field 6 equals "Open":

```plaintext
{6.EX.'Open'}
```

Combine multiple conditions with `AND` or `OR` (always uppercase), grouped with parentheses:

```plaintext
({6.EX.'Open'}OR{6.EX.'In Progress'})AND{13.OAF.'01-01-2025'}
```

### Common operators

| Operator | What it does | Example |
| --- | --- | --- |
| `EX` | Equals exactly | `{6.EX.'Open'}` |
| `XEX` | Not equal | `{6.XEX.'Closed'}` |
| `CT` | Contains substring (text) | `{12.CT.'urgent'}` |
| `SW` | Starts with (text) | `{12.SW.'INV-'}` |
| `BF` / `AF` / `OAF` | Before / after / on or after (date) | `{13.OAF.'01-01-2025'}` |
| `LT` / `LTE` / `GT` / `GTE` | Less / less-or-equal / greater / greater-or-equal (numbers) | `{19.GT.'1000'}` |
| `HAS` | Contains a value (multi-select, List-User) | `{20.HAS.'alice@example.com'}` |
| `IR` | In a relative date range (`'today'`, `'yesterday'`, and similar) | `{13.IR.'today'}` |

For the full operator list, see [Components of a Query](/v1/docs/components-of-a-query).

### Compare one field to another

Use `_FID_n` as the matching value to compare two fields on the same record:

```plaintext
{6.EX._FID_8}
```

Returns records where field 6 equals field 8.

### Embed Jinja inside a query

The matching value portion can contain Jinja, evaluated at runtime. The outer query syntax stays the same; the inner `{{…}}` produces the value.

Find records modified in the last 5 minutes:

```plaintext
{2.OAF.'{{time.now + time.delta(minutes=-5)}}'}
```

Find records owned by the user who triggered the run:

```plaintext
{8.EX.'{{a.context.user}}'}
```

Find records matching a value from step `a`:

```plaintext
{6.EX.'{{a.customer_id}}'}
```

Two things that don't work inside a query:

- `$prev`—only works in regular Jinja value expressions, not in query strings
- Multi-line Jinja with `{% … %}` statements—keep it to a single `{{ … }}` expression

## What isn't supported

Things you might expect to work but don't:

- `{% break %}` and `{% continue %}` inside loops. Use `select` / `reject` filters to filter the list before looping, or wrap the loop body in an `{% if %}`.
- `{% include %}`, `{% import %}`, `{% extends %}`—Pipelines doesn't allow template inheritance.
- Importing Python modules.
- Reading or writing files outside the pipeline.
- The `do` extension (`{% do … %}`).

## Reserved words

A few attribute names conflict with built-in Jinja or Python concepts and can't be reached with dot notation. Use bracket notation instead.

| Field name | Use this instead of `b.field_name` |
| --- | --- |
| `items` | `b['items']` |
| `keys` | `b['keys']` |
| `values` | `b['values']` |
| `update` | `b['update']` |
