Docs / Strand / templating/jinja2

Jinja2 Templating

Jinja2 is a powerful templating engine integrated into Strand workflows.

What is Jinja2?

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:

jinja

{{ variable }}
{{ expression }}
Example: Simple Example
jinja

Hello {{ payload.user_name }}!

Accessing Data

Current Event

jinja

{{ payload.field_name }}
{{ meta.timestamp }}
{{ event.payload.user_id }}
Automatic Data Passing

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:

jinja

{{ payload.field_name }}

For non-directly connected nodes:

jinja

{{ steps.node_id.output_payload.field }}
{{ steps.node_id.output_meta.timestamp }}
{{ steps.node_id.output_count }}
Finding Node IDs

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

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

jinja

{{ initial.payload.data }}
{{ initial.meta.workflow_name }}
{{ initial.meta.trigger_source }}
{{ initial.meta.triggered_at }}

Workflow Variables

jinja

{{ 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 }}`
Filter Chaining

You can chain multiple filters together:

jinja

{{ payload.name | upper | truncate(20) }}

Conditionals

Ternary Operator

jinja

{{ 'admin' if payload.role == 'admin' else 'user' }}

If/Else Blocks

jinja

{% if steps.auth.output_payload.verified %}
  {{ steps.admin_data.output_payload }}
{% else %}
  {{ steps.user_data.output_payload }}
{% endif %}
Example: Complex Conditional
jinja

{% if payload.temperature > 25 %}
  {{ 'Hot: ' + (payload.temperature | string) }}
{% elif payload.temperature < 10 %}
  {{ 'Cold: ' + (payload.temperature | string) }}
{% else %}
  {{ 'Normal: ' + (payload.temperature | string) }}
{% endif %}

Loops

jinja

{% for item in payload.items %}
  {{ item.name }}
{% endfor %}
Loop Variables

Jinja2 provides special loop variables:

  • loop.index - Current iteration (1-indexed)
  • loop.index0 - Current iteration (0-indexed)
  • loop.first - True if first iteration
  • loop.last - True if last iteration

Using in Node Configurations

HTTP Request URL

Direct connection:

jinja

https://api.example.com/users/{{ payload.id }}/profile

Non-direct access:

jinja

https://api.example.com/users/{{ steps.user_lookup.output_payload.id }}/profile

HTTP Request Body

Direct connection:

jinja

{
  "user_id": "{{ payload.id }}",
  "temperature": {{ payload.temp }},
  "timestamp": "{{ meta.received_at }}"
}

Non-direct access:

jinja

{
  "user_id": "{{ steps.user_lookup.output_payload.id }}",
  "temperature": {{ steps.sensor.output_payload.temp }},
  "timestamp": "{{ meta.received_at }}"
}
JSON in Templates

When using JSON in templates, make sure strings are quoted. Numbers and booleans don't need quotes.

Transform Mapping

Direct connection:

jinja

{
  "full_name": "{{ payload.first_name }} {{ payload.last_name }}",
  "email": "{{ payload.email }}"
}

Non-direct access:

jinja

{
  "full_name": "{{ steps.user_lookup.output_payload.first_name }} {{ steps.user_lookup.output_payload.last_name }}",
  "email": "{{ steps.user_lookup.output_payload.email }}"
}

Comments

jinja

{# This is a comment that won't appear in the output #}
Documenting Templates

Use comments to explain complex template logic:

jinja

{# 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
Always guard optional data

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:

jinja

{{ payload.user.email | default('[email protected]') }}

For nested access, guard each level or check existence first:

jinja

{% 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

1

Use descriptive node IDs

Makes references clearer (e.g., user_lookup vs node1)

2

Check for existence

Use default filter for safe access

3

Test incrementally

Build and test templates step by step

4

Use comments

Document complex logic for future reference

5

Keep it simple

Break complex templates into smaller, reusable parts

Pro Tips
  • 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