This article covers common Jinja expressions organized by task and Quickbase field type. Each entry includes the expression and what it produces. Use the table of contents or Ctrl+F to find what you need.
In this article
By task:
Audit trail with
$prev
By Quickbase field type:
How step output varies by channel
You'll see two patterns in these expressions for reaching data from earlier steps:
a.field_name—for Quickbase trigger steps, lookup steps, and most other channels (the simpler case)b.json.data[0]['7'].value—for steps that talk to the Quickbase REST API directly (typically Make a Request steps against the Quickbase channel)
API-style steps wrap their output in a JSON structure (.json.data), and the fields inside are reached by field ID (a number like 7), not by field name. The two patterns return the same underlying data, just with different access syntax depending on which step produced it.
Whenever an expression uses field IDs like '6', '7', or '14', replace them with the actual field IDs from your table.
Dates and time
Add or subtract days from today
{{time.today + time.delta(days=7)}} # 7 days from today
{{time.today - time.delta(days=30)}} # 30 days ago
{{time.today + time.delta(months=1)}} # one month from todayCalculate 5 business days from today
{% if (time.now | timezone('America/New_York')).isoweekday() <= 5 %}
{{time.now | timezone('America/New_York') + time.delta(days=7)}}
{% elif (time.now | timezone('America/New_York')).isoweekday() == 6 %}
{{time.now | timezone('America/New_York') + time.delta(days=6)}}
{% else %}
{{time.now | timezone('America/New_York') + time.delta(days=5)}}
{% endif %}Adds 5 weekdays. If today is a weekend, adjusts so the result still lands on a weekday. .isoweekday() returns 1 (Monday) through 7 (Sunday).
Get the first and last day of the current month
{{time.today - time.delta(day=1)}}
{{(time.today + time.delta(months=1) - time.delta(day=1)) - time.delta(days=1)}}Note the singular day=1 (sets the day-of-month to 1) versus plural days= (adds days).
Convert a Unix timestamp (milliseconds) to a formatted date
{{(((a.timestamp | int) // 1000) | timestamp_to_time | timezone('America/Chicago')).strftime('%Y-%m-%d')}}Webhook payloads often deliver timestamps in milliseconds. Cast to int, divide by 1000 to get seconds, convert to time, apply a timezone, then format.
Parse a date string and add a year
{{(time.parse(a.date) + time.delta(years=1)).strftime('%Y-%m-%d')}}When a date arrives as a string (from a callable pipeline, a webhook, or an API response), parse it back into a real date before doing date math.
Convert 12-hour time to 24-hour format
{{time.parse(a.time_field).strftime('%H:%M')}}01:35 PM becomes 13:35.
Conditionals
Branch on a multi-select value
{% if "Enterprise" in a.pillar %}Enterprise
{% elif "Platform" in a.pillar %}Platform
{% elif "Services" in a.pillar %}Services
{% endif %}The in test works on both arrays (multi-select Text from a Quickbase trigger) and comma-separated strings (webhook payloads). Returns the first match—Jinja stops evaluating once a condition is true.
Clear a field on a specific value
{% if a.priority == 'No priority' %}{{CLEAR}}
{% else %}{{a.priority}}
{% endif %}If the source has a "No priority" sentinel, clear the destination field instead of writing the sentinel.
Increment one field unless it matches another
{% if a.field1 == a.field2 %}1
{% else %}{{a.field2 + 1}}
{% endif %}Text
Take a substring
{{a.text_field[2:6]}} # characters at index 2–5
{{a.text_field[0:5]}} # first 5 characters
{{a.text_field[-5:]}} # last 5 charactersIndexes start at 0. The end index is "stop before."
Split a string and take one piece
{{a.title.split(' - ')[1]}} # second piece
{{a.email.split('@')[1]}} # domainProject_Services - 23 - title → 23.
Strip a currency symbol
{{a.price | replace('$', '') | replace(',', '') | float}}$1,250.00 → 1250.0.
Numbers
Sum a field across all records, ignoring nulls
{% set total = namespace(value=0) %}
{% for r in b.json.data %}
{% set total.value = total.value + (r['19'].value | default(0, true)) %}
{% endfor %}
{{total.value}}b | sum(attribute='amount') fails when any value is null. This pattern walks the list manually and substitutes 0 for missing values. The namespace keeps the running total alive across loop iterations.
Generate a random number in a range
{{range(1000, 9999) | random}}Returns a 4-digit code. Useful for locker assignments, voucher codes, or short tokens.
Lists and aggregation
Remove duplicates, sort, and join
{% set val = namespace(value=[]) %}
{% for rec in b.json.data %}
{% set val.value = val.value | append(rec['3'].value) %}
{% endfor %}
{{(val.value | unique | list | sort) | join(', ')}}After a Quickbase search step that returns multiple records, collect field 3 across all of them, drop duplicates, sort, and join into a single comma-separated string.
Find what's in list A but not in list B
{% set listA = b | map(attribute='field_id') | map('int') | list %}
{% set listB = c.json | map(attribute='id') | list %}
{% set difference = listA | reject('in', listB) | list %}Use when comparing two datasets to find what's missing or new.
Filter records by a field value
{{b | selectattr('status', 'equalto', 'Open') | list}}Keep only records whose status is Open. Use rejectattr to drop instead.
Extract one field from every record
{{b | map(attribute='email') | list}}[{firstName: "Alex", ...}, {firstName: "Jordan", ...}] becomes ["Alex", "Jordan"].
JSON and payload building
Defensive nested access
{{a.body.get('user', {}).get('email', 'unknown')}}When the JSON structure may or may not contain a field, use .get(key, default) at each level to avoid NoneType errors.
Quickbase upsert with mergeFieldId
{
"to": "DBID",
"mergeFieldId": 18,
"data": [
{
"18": {"value": "{{a.customer_id}}"},
"26": {"value": "{{a.amount}}"}
}
]
}mergeFieldId tells Quickbase to update an existing record when the key matches, or create a new one when it doesn't. Numeric value, not quoted.
Create one record per multi-select value
{
"to": "DBID",
"data": [
{% for value in a.tags %}
{"7": {"value": "{{value}}"}}{{',' if not loop.last}}
{% endfor %}
]
}If a.tags is Cat, Dog, Camel, the payload creates three records—one for each value. Quickbase API payloads are capped at approximately 1 MB; very large multi-selects may need chunking.
Audit trail with $prev
Log a row whenever the status field changes
{% if a.status != a.$prev.status %}
{
"to": "DBID",
"data": [{
"6": {"value": "{{a.id}}"},
"7": {"value": "status"},
"8": {"value": "{{a.$prev.status}}"},
"9": {"value": "{{a.status}}"},
"10": {"value": "{{time.now}}"},
"11": {"value": "{{a.context.user}}"}
}]
}
{% endif %}Send the resulting JSON to a log table via the Quickbase Create Records API. Replace DBID with your log table's ID, and replace field IDs 6, 7, 8, 9, 10, 11 with the field IDs of Record ID, Field Name, Old Value, New Value, Changed At, and Changed By in your log table.
Wrap each tracked field in its own {% if %} block. $prev only exists on update triggers, not on Create or Delete.
Embedded Jinja in Advanced Query
These go in the Advanced Query field of a Quickbase search step, not in a regular Jinja field. The outer braces are Quickbase query syntax; the inner {{…}} is Jinja. See the Jinja reference for the full syntax.
Records modified in the last 5 minutes
{2.OAF.'{{time.now + time.delta(minutes=-5)}}'}Field 2 is Last Modified. OAF = on or after.
Records owned by the user who triggered the run
{8.EX.'{{a.context.user}}'}Records where field 6 matches a value from step a
{6.EX.'{{a.customer_id}}'}The runtime object in alerts
Error alert with a direct link back
Pipeline "{{runtime.pipeline_name}}" failed.
View activity: {{runtime.activity_url}}
Triggered at: {{runtime.triggered_at}}Useful in a Slack message, an on-call notification, or an email body. runtime.activity_url opens the activity log filtered to this exact run.
Patterns for specific Quickbase field types
Address
A Quickbase address field has a parent label and several subfields: street1, street2, city, state, postal_code, country.
{{a.shipping_address.city}}
{{a['Mailing Address'].postal_code}} # parent label with spaces
{{a.shipping_address.street1}}, {{a.shipping_address.city}}, {{a.shipping_address.state}} {{a.shipping_address.postal_code}}When the parent label has spaces, use bracket notation around the parent, then dot notation for the subfield.
User
User fields expose email, id, and name:
{{a.owner.email}} # user@example.com
{{a.owner.id}} # 57568161.bg5i
{{a.owner.name}} # Jane DoeFor Quickbase API payloads writing back to a user field, prefer .email; .id sometimes silently fails to apply.
List-User
List-User fields arrive as a list of user objects. Join them for a payload:
{{a.list_user | join(';')}}Add another user to the list:
{{a.list_user | join(';') ~ ';' ~ a.user.email}}Remove a specific user:
{{a.user_list | rejectattr('email', 'eq', 'demo@testing.com') | join(',')}}Note: Send the email address, not the user ID. Sending
.idvia this pattern has been observed to wipe the field rather than update it.
Multi-select Text
Multi-select fields are comma-separated, but Jinja exposes them as an iterable. Loop through values:
{% for tag in a.tags %}
{{tag}}
{% endfor %}Check whether the field contains a specific value:
{% if "Enterprise" in a.pillar %}...{% endif %}To build a payload that creates one record per value, see Create one record per multi-select value.
File Attachment
To upsert a file to a file attachment field via the Quickbase API, encode the file as base64 and include the filename:
{
"to": "DBID",
"data": [{
"5": {
"value": {
"fileName": "{{a.file.name}}",
"data": "{{a.file.content | base64_encode}}"
}
}
}]
}For pipelines that pass files between channels (Quickbase to another system), Pipelines uses a file transfer handle automatically—no Jinja needed.
Rich Text
Rich Text fields can contain HTML. Strip it for plain-text output:
{{a.notes | striptags}} # remove all HTML
{{a.notes | text}} # broader cleanup of HTML and formatting
{{a.notes | html2text}} # convert HTML to MarkdownDuration
Quickbase duration fields are stored in milliseconds. Convert with integer division (//):
{{a.duration // 60000}} # minutes
{{a.duration // 3600000}} # hours
{{a.duration // 86400000}} # daysA duration of 3600000 returns 1 for hours.
Date/Time
When a date field is passed to a callable pipeline, it arrives as a string. Parse it back to a real date before doing date math:
{{(time.parse(a.received_date) + time.delta(days=30)).strftime('%Y-%m-%d')}}Output just the date portion of a Date/Time field:
{{a.created_at.date()}} # date only
{{a.created_at | date_ymd}} # 2025-01-15
{{a.created_at.strftime('%B %d, %Y')}} # January 15, 2025