Docs / Strand / templating/jinja2
Jinja2 Templating
Jinja2 is a powerful templating engine integrated into Strand workflows.
Jinja2 is a modern templating engine for Python, inspired by Django's templates. It provides a powerful syntax for building dynamic strings, conditionals, and loops.
All node types in Strand support full Jinja2 templating with access to:
- Previous step outputs (
steps.node_id.output_payload) - Current event data (
payload,meta) - Workflow variables (
variables.key) - JSONPath queries for complex data extraction
- All Jinja2 filters and expressions
Basic Syntax
Jinja2 uses double curly braces for expressions:
{{ variable }}
{{ expression }}
Hello {{ payload.user_name }}!
Accessing Data
Current Event
{{ payload.field_name }}
{{ meta.timestamp }}
{{ event.payload.user_id }}
When nodes are directly connected, the previous node's output is automatically available as payload. No need to use steps.{node_id}.output_payload for direct connections!
Previous Steps
For directly connected nodes:
{{ payload.field_name }}
For non-directly connected nodes:
{{ steps.node_id.output_payload.field }}
{{ steps.node_id.output_meta.timestamp }}
{{ steps.node_id.output_count }}
Find node IDs in the Node Inspector when you select a node. Use payload for direct connections, steps.{node_id}.output_payload for non-direct access.
Node IDs are short alphanumeric strings (e.g., node_a1b2c3d4) that work directly with dot notation:
- ✅
{{ steps.node_a1b2c3d4.output_payload }}
Older workflows may have hyphens in node IDs. For those, use bracket notation:
- ✅
{{ steps['node-1234567890'].output_payload }} - ❌
{{ steps.node-1234567890.output_payload }}(hyphens are interpreted as subtraction)
Initial Event
{{ initial.payload.data }}
{{ initial.meta.workflow_name }}
{{ initial.meta.trigger_source }}
{{ initial.meta.triggered_at }}
Workflow Variables
{{ variables.api_base_url }}
{{ variables.timeout | default(30) }}
Filters
Jinja2 provides many built-in filters for data transformation:
Common Filters
| Filter | Description | Example | |
|---|---|---|---|
default |
Provide fallback value | `{{ value \ | default('N/A') }}` |
tojson |
Convert to JSON string | `{{ data \ | tojson }}` |
upper |
Convert to uppercase | `{{ name \ | upper }}` |
lower |
Convert to lowercase | `{{ email \ | lower }}` |
truncate |
Truncate string | `{{ desc \ | truncate(50) }}` |
round |
Round number | `{{ price \ | round(2) }}` |
length |
Get length | `{{ items \ | length }}` |
You can chain multiple filters together:
{{ payload.name | upper | truncate(20) }}
Conditionals
Ternary Operator
{{ 'admin' if payload.role == 'admin' else 'user' }}
If/Else Blocks
{% if steps.auth.output_payload.verified %}
{{ steps.admin_data.output_payload }}
{% else %}
{{ steps.user_data.output_payload }}
{% endif %}
{% if payload.temperature > 25 %}
{{ 'Hot: ' + (payload.temperature | string) }}
{% elif payload.temperature < 10 %}
{{ 'Cold: ' + (payload.temperature | string) }}
{% else %}
{{ 'Normal: ' + (payload.temperature | string) }}
{% endif %}
Loops
{% for item in payload.items %}
{{ item.name }}
{% endfor %}
Jinja2 provides special loop variables:
loop.index- Current iteration (1-indexed)loop.index0- Current iteration (0-indexed)loop.first- True if first iterationloop.last- True if last iteration
Using in Node Configurations
HTTP Request URL
Direct connection:
https://api.example.com/users/{{ payload.id }}/profile
Non-direct access:
https://api.example.com/users/{{ steps.user_lookup.output_payload.id }}/profile
HTTP Request Body
Direct connection:
{
"user_id": "{{ payload.id }}",
"temperature": {{ payload.temp }},
"timestamp": "{{ meta.received_at }}"
}
Non-direct access:
{
"user_id": "{{ steps.user_lookup.output_payload.id }}",
"temperature": {{ steps.sensor.output_payload.temp }},
"timestamp": "{{ meta.received_at }}"
}
When using JSON in templates, make sure strings are quoted. Numbers and booleans don't need quotes.
Transform Mapping
Direct connection:
{
"full_name": "{{ payload.first_name }} {{ payload.last_name }}",
"email": "{{ payload.email }}"
}
Non-direct access:
{
"full_name": "{{ steps.user_lookup.output_payload.first_name }} {{ steps.user_lookup.output_payload.last_name }}",
"email": "{{ steps.user_lookup.output_payload.email }}"
}
Comments
{# This is a comment that won't appear in the output #}
Use comments to explain complex template logic:
{# Calculate total including tax #}
{{ payload.price * 1.1 }}
Error Handling
Templates render in strict mode: referencing a missing variable or key (for example {{ payload.user.email }} when email is absent) raises an error rather than producing an empty string. What happens next depends on where the template is used:
| Where the template runs | Behavior on a missing reference |
|---|---|
| If/Else condition (logic node) | Step fails with an error |
Sub-workflow input (flow.call data) |
Step fails with an error |
| Transform mapping | That field is set to null (the step continues) |
| Filter / routing condition | Evaluates to false |
| Other node config (HTTP body, notify content, etc.) | The unresolved template text is passed through as-is |
Because strict mode raises on missing keys, treat the default filter (or an existence check) as required whenever a field might be absent. This keeps conditions, sub-workflow calls, and transforms from failing or producing null:
{{ payload.user.email | default('[email protected]') }}
For nested access, guard each level or check existence first:
{% if payload.user is defined and payload.user.email is defined %}
{{ payload.user.email }}
{% endif %}
Check execution logs for detailed error messages when a step fails on a template.
Best Practices
Use descriptive node IDs
Makes references clearer (e.g., user_lookup vs node1)
Check for existence
Use default filter for safe access
Test incrementally
Build and test templates step by step
Use comments
Document complex logic for future reference
Keep it simple
Break complex templates into smaller, reusable parts
- Use the Node Inspector to see the exact node ID for templating
- Test templates with the "Run Workflow" feature before deploying
- Check execution logs to see actual template output values
Tendrl