---
title: "Start with Jinja in Pipelines"
slug: "start-with-jinja-in-pipelines"
updated: 2026-06-16T19:50:41Z
published: 2026-06-16T19:50:41Z
canonical: "help.quickbase.com/start-with-jinja-in-pipelines"
---

> ## 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.

# Start with Jinja in Pipelines

## What is Jinja and why do I need it?

A pipeline automates tasks by moving data between steps. When a trigger happens in one place an action happens in another. But, the data moving between steps rarely fits perfectly. You'll often need to combine two fields, format a date, branch on a condition, or build a list.

Jinja is a templating language that fills in placeholders with real data when the pipeline runs. Write it into any field that accepts text, wrapped in curly braces, and Pipelines evaluates it when the run happens.

Use Jinja any time you want to:

- Combine values from earlier steps, ex. combining a first name and last name.
- Change a value before sending it on, ex. formatting a numerical value as a date.
- Make a decision, ex. writing *VIP* if the customer meets a set criteria, or leaving it blank if they don’t.
- Handle a list, ex. having one line per record returned by a search step.

The moment a pipeline needs to use data from a previous step or react to what's coming in, you need *something* in place of static text. Jinja is that something. You don't have to learn all of Jinja to get value from it—most pipelines use a handful of common patterns.

## Glossary

| Term | What it means |
| --- | --- |
| **Step** | One action in a pipeline Ex: *Look up a record* |
| **Field** | A piece of data on a record Ex: C*ustomer name* |
| **Trigger** | The event that starts a pipeline Ex: A record was created in a table |
| **Search step** | A step that returns a list of records matching a filter |
| **Designer** | The visual editor where you build a pipeline |
| **Code mode** | When you select the **</>** icon on a field to type a raw value yourself |
| **Activity log** | A history of every pipeline run, with every step's output—open via the pipeline's **More** menu (3-dot icon), then select **View activity** |

## What Jinja looks like

Here's a Jinja expression:

```plaintext
{{a.first_name}} {{a.last_name}}
```

In a pipeline step, this prints the first and last names from the previous step. The [Reference steps](/v1/docs/start-with-jinja-in-pipelines#reference-steps) section of this article explains what `a` means. The [Common tasks](/v1/docs/start-with-jinja-in-pipelines#common-tasks) table shows how to combine values, and [Debug your pipeline](/v1/docs/start-with-jinja-in-pipelines#debug-your-pipeline) covers what to do when things go wrong.

Jinja isn't the Quickbase formula language. The two share goals but use different syntax. `IfThenElse(...)` is a formula; `{% if ... %}...{% endif %}` is Jinja.

## Common tasks

| I want to… | Try this |
| --- | --- |
| Combine two fields | `{{a.first_name}} {{a.last_name}}` |
| Concatenate without spaces | `{{a.first ~ a.last}}` |
| Uppercase a value | `{{a.name \| upper}}` |
| Round a number | `{{a.amount \| round(2)}}` |
| Today's date | `{{time.today}}` |
| Date 7 days from now | `{{time.today + time.delta(days=7)}}` |
| If / else | `{% if a.amount &gt; 100 %}High{% else %}Low{% endif %}` |
| Loop through a list | `{% for r in b %}{{r.name}}{% endfor %}` |
| Fall back if empty | `{{a.note \| default('No notes')}}` |
| Clear a field | `{{CLEAR}}` |
| Detect a changed field | `{% if a.status != a.$prev.status %}…{% endif %}` |

For more, see [Common Jinja expressions](/v1/docs/common-jinja-expressions).

## Where to write Jinja

Most fields in a step accept Jinja. Select the code icon **</>** next to a field to switch it into code mode, then type your expression. Once you type `{{`, the field starts validating and formatting as you go.

To start, drag a field tile from the **Variables** panel into a code-mode field. The Designer writes the dot notation for you.

> [!NOTE]
> **Note:** A few fields don't accept Jinja and use a different language instead. The most common case is **Advanced Query** / filter fields on Quickbase search and trigger steps. Those use Quickbase query syntax, not Jinja. See [Jinja reference](/docs/start-with-jinja-in-pipelines#) for the syntax. If your Jinja stops working in a filter field, that's probably why.

## Reference steps

Every step in a pipeline gets a letter: the first is `a`, the second is `b`, the third is `c`, and so on.

When you write `b.customer_name`, it means: the `customer_name` field from the second step.

For a Record Created trigger, step `a` is the record that fired the trigger, and `a.field_name` gets any field on it.

### Three ways to reach a field

```plaintext
{{a.first_name}}                   # standard
{{a['Date Created']}}              # field name has spaces
{{a.get('phone', 'No phone')}}     # with a default if missing
```

Use brackets when the field name has spaces or matches a reserved word like `items`.

For address fields, use brackets when the parent label has spaces:

```plaintext
{{a['Mailing Address'].city}}
```

## Single records vs. lists

Triggers and lookups return one record. `a.field` works directly.

Searches and Get All steps return a list, even if only one record matches. Don't dot-access them—use the first item, or loop:

```plaintext
{{b[0].customer_name}}             # first record

{% for r in b %}                   # all records
{{r.customer_name}}
{% endfor %}
```

If you see an error like `TypeError: 'list' object has no attribute…` in the activity log, you tried to dot-access a list when you should have looped or indexed.

In the Designer, list-returning steps show a green indicator on their output and single-record steps show blue.

## Track what changed with `$prev`

When a record is updated, compare current and previous values with `$prev`:

```plaintext
{% if a.status != a.$prev.status %}
Status changed from {{a.$prev.status}} to {{a.status}}.
{% endif %}
```

> [!WARNING]
> **Warning:** `$prev` only works on **Record Updated** triggers—not on Create or Delete. It goes between the step letter and the field: `a.$prev.field_name`, not `$prev.a.field_name`. It also doesn't work inside Quickbase Advanced Query filter strings.

A common use is an audit-log pipeline that writes a row per field change.

## Empty a field intentionally with `{{CLEAR}}`

By default, Pipelines refuses to write an empty value to a field. It silently skips it instead, to protect you from accidentally wiping data.

If you actually want to clear a field, use `{{CLEAR}}`:

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

`{{CLEAR}}` only works in a step that writes data back—typically **Create** or **Update**.

## Conditionals and loops

### Use conditionals

A conditional asks a yes/no question and follows one path if true, another if false. Is the amount over $1,000? Label it "Big." Is it not? Check the next condition.

Example: Label every order as "Big," "Medium," or "Small" depending on the amount.

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

*If the amount is bigger than 1,000, write "Big." Otherwise, if it's bigger than 100, write "Medium." Otherwise, write "Small."*

The pattern is always:

- `{% if … %}`—the question to check
- `{% elif … %}`—(optional) another question if the first one was no
- `{% else %}`—(optional) what to do if nothing matched
- `{% endif %}`—closes the block

You can also use `if` on its own to write something only when a condition is true:

```plaintext
{% if a.is_vip %}
This customer is a VIP.
{% endif %}
```

### Use loops

A loop is how you handle a list of things, like 1 line per customer, 1 entry per order, or 1 row per search result.

Example: A search step returned a list of customers, and you want to print every customer's name on its own line.

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

*For each customer in the list* `b`, write that customer's name.

The pattern is always:

- `{% for X in Y %}`—go through every item in the list `Y`. The name `X` is a placeholder; `customer` in this example, but `r`, `item`, or any valid variable name works.
- The lines in the middle—what to do for each item, using the placeholder name (`customer`) to reach its fields
- `{% endfor %}`—closes the block

While inside the loop, Jinja gives you a few useful helpers:

- `loop.index`—position of the current item, counting from 1 (the third item has `loop.index` of 3)
- `loop.last`—true on the last item only
- `loop.length`—the total number of items

Use `loop.last` to add a comma after every item except the last:

```plaintext
{% for c in b %}{{c.name}}{% if not loop.last %}, {% endif %}{% endfor %}
```

That writes *Alice, Bob, Carol* instead of *Alice, Bob, Carol,*. For the full set of loop helpers, see the [Jinja reference](/v1/docs/jinja-reference).

### Why the curly-brace patterns are different

Both patterns use `{% %}`—curly braces with a percent sign. That's because they're *instructions*, not values.

- `{{ ... }}`—*show me this value* (double curly braces)
- `{% ... %}`—*do something*, like an if or a loop (curly braces with a percent)

## The runtime object

Every pipeline run has a `runtime` object with information about itself:

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

Available properties: `pipeline_name`, `pipeline_id`, `pipeline_url`, `run_id`, `activity_url`, `triggered_at`, `user_id`.

On Quickbase triggers, you also get a `context` object with details about the table and user:

```plaintext
Updated in {{a.context.table_name}} ({{a.context.app_name}})
at {{a.context.occurred_at}} by {{a.context.user}}.
```

## Debug your pipeline

Open the pipeline, then select the **More** menu (3-dot icon) > **View activity**. The activity log shows every step's rendered Jinja, color-coded by data type.

| Symptom | Likely cause | Fix |
| --- | --- | --- |
| Output renders as literal `None` | You combined a null value with text | Add `\| default('')` |
| Output missing from the log | Empty value silently skipped | Use `{{CLEAR}}` if intentional, or `default` to fall back |
| `Validation error: Incorrect template` | Jinja syntax error | Check for missing `{% endif %}` or `{% endfor %}`, unmatched quotes, or stray whitespace in JSON |
| `TypeError: 'NoneType' object has no attribute X` | You reached for a field on a null value | Guard with `{% if a.field is defined and a.field is not none %}` |

## Use AI to draft expressions

Once you're comfortable with the basics, the Pipelines AI can write Jinja for you. Open code mode on any field and select the **AI suggestion** icon. Describe what you want in plain language (for example, *set this to 7 days after the trigger date* or *build a list of customer emails separated by semicolons*) and the AI proposes a Jinja expression you can review and edit before applying.

The expressions it generates make more sense, and are easier to review and adjust, once you're familiar with the patterns in this article.

## Where to go next

- [Full filter and test reference](/v1/docs/jinja-reference#filters-by-category)—Jinja reference
- [Ready-to-use patterns by task or field type](/v1/docs/common-jinja-expressions)—Common Jinja expressions
