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('unknown@example.com') }}
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#
Copy node references from the inspector
Node IDs are generated (node_a1b2c3d4) and cannot be renamed; the node inspector's Data access reference gives you the exact expression. Use node labels for readability
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 editor's Run button before deploying
- Check execution logs to see actual template output values
Tendrl