Jinja Basics

Last Updated: October 24, 2026

Jinja is a templating language, with a syntax like Python's, that D3 playbooks use to retrieve, change and present data. You write Jinja in playbook tasks, for example in a Data Formatter task. Every example on this page was run in a Data Formatter task with Test Playbook, and the output shown is the task's Return Data. See Data Formatter Tasks and Playbook Testing and Task Node Re-runs.

An example in a Data Formatter task

A Data Formatter task named Jinja Test with a set statement and an expression

Its return data

out-var.png

Data and Expressions

Data Types

A data type defines a value's structure and what you can do with it.

Group

Type

Example

Primitives (one value)

String (str)

"D3 Security"

Integer (int)

2025

Float (float)

3.14159263

Boolean (bool)

True or true, False or false

None (NoneType)

None or none

Collections (several values)

List (list), an ordered collection

[ 1, 2, 3, 4 ]

Tuple (tuple), an ordered collection that cannot change

( 1, 2, 3 )

Dictionary (dict), key-value pairs

{ "company": "D3 Security", "employees": 150 }

Special

Undefined (Undefined), a variable with no value

{{ unknown_variable }}

  • In the task output, a tuple is shown as a list: {% set t = ( 1, 2, 3 ) %}{{ t }} returns [ 1, 2, 3 ].

  • Booleans and None are written in title case or lower case. TRUE and NONE are undefined variables and render as nothing.

  • An undefined variable does not make the task fail, and it produces no return data: the task's details have no Return Data tab.

Assigning Variables

The {% set %} statement defines a variable and assigns it a value. Use {{ variable_name }} to show the value in the task output.

Example 1: assigning and referencing a variable in one task

{% set user = {
    "username": "Alice",
    "department": "Security Operations Center",
    "role": "Incident Responder",
    "access_level": "Tier-1",
    "last_login": "2024-02-07T10:30:00Z"
} %}

{{ user }}

Output:

{
  "username": "Alice",

  "department": "Security Operations Center",

  "role": "Incident Responder",

  "access_level": "Tier-1",

  "last_login": "2024-02-07T10:30:00Z"

}

The task in this example is named Defining and Assigning Variables. Example 2: reading another task's output and overriding a value

{% set user = PlaybookData | jsonpath('$.["Defining and Assigning Variables"].returnData') %}

{% set updated_user = {
    "username": user.username,
    "department": user.department,
    "role": user.role,
    "access_level": "Tier-2",
    "last_login": user.last_login
} %}

{{ updated_user }}

Output:

{
  "username": "Alice",

  "department": "Security Operations Center",

  "role": "Incident Responder",

  "access_level": "Tier-2",

  "last_login": "2024-02-07T10:30:00Z"

}

This task reads the return data of the Defining and Assigning Variables task from the playbook data, and sets a new access_level. See Dynamically Selecting Data.

Expressions

An expression is anything that evaluates to one value: literal values, variable references, arithmetic, logical expressions, comparisons, macro calls, filters and tests.

Literal values

{{ "D3 Security" }}  
{{ 2025 }}  
{{ true }}
{{ none }}

Output:

D3 Security
2025
True
None

Variable references

{% set threat_level = "Critical" %}

Threat Level: {{ threat_level }}

Output:

Threat Level: Critical

Arithmetic

{% set failed_attempts = 5 %}

Lockout Threshold: {{ failed_attempts * 2 }}

Output:

Lockout Threshold: 10

Logical expressions (and, or, not)

{% set is_malicious = True %}
{% set is_whitelisted = False %}

Suspicious: {{ is_malicious and not is_whitelisted }}

Output:

Suspicious: True

Comparisons return True or False.

Operator

Description

Example

==

Equal to

{{ alert.severity == "Critical" }}

!=

Not equal to

{{ user.role != "admin" }}

>

Greater than

{{ failed_logins > 5 }}

<

Less than

{{ incident_count < 10 }}

>=

Greater than or equal to

{{ risk_score >= 3 }}

<=

Less than or equal to

{{ password_age <= 30 }}

A comparison

{% set x = 10 %}
{% set y = 5 %}

x greater than y: {{ x > y }}

Output:

x greater than y: True

Macro (function) calls

{% macro greet_user(username) %}
    Hello {{ username }}!
{% endmacro %}

{{ greet_user("Alice") }}
{{ greet_user("Emily") }}

Output:

Hello Alice!

Hello Emily!

Filters

{% set log_entry = "User jdoe logged in from 192.168.1.100 at 7:30 AM." %}

Sanitized Log: {{ log_entry 
    | replace("jdoe", "[REDACTED_USER]") 
    | replace("192.168.1.100", "[REDACTED_IP]") }}

Output:

Sanitized Log: User [REDACTED_USER] logged in from [REDACTED_IP] at 7:30 AM.

Tests check a property of a value and return True or False.

A test

{% set user_role = "admin" %}

Admin Role Defined: {{ user_role is defined }}  
Guest Role Defined: {{ guest_role is defined }}

Output:

Admin Role Defined: True
Guest Role Defined: False

Test

Description

Example

Output

is defined

Checks whether a variable exists.

{{ user_role is defined }}

False

is not defined

Checks whether a variable does not exist.

{{ unknown_var is not defined }}

True

is none

Checks whether a value is None.

{% set value = none %} {{ value is none }}

True

is number

Checks whether a value is a number (integer or float).

{% set x = 3.14159263 %} {{ x is number }}

True

is string

Checks whether a value is a string.

{% set y = "D3 Security" %} {{ y is string }}

True

is sequence

Checks whether a value is a sequence (list, tuple or string).

{% set alerts = ["Phishing", "DDoS"] %} {{ alerts is sequence }}

True

is even

Checks whether a number is even.

{% set port = 443 %} {{ port is even }}

False

is odd

Checks whether a number is odd.

{% set port = 443 %} {{ port is odd }}

True

is greaterthan

Checks whether a value is greater than another.

{% set severity = 5 %} {{ severity is greaterthan 3 }}

True

is lessthan

Checks whether a value is less than another.

{% set severity = 2 %} {{ severity is lessthan 5 }}

True

is in

Checks whether an item is in a list or string.

{% set critical_alerts = ["Ransomware", "APT"] %} {{ "Ransomware" is in critical_alerts }}

True

Control Structures

Filters

Filters change a value. Chain them with the pipe: {{ variable | filter1(arguments) | filter2 }}. Jinja2's built-in filters are listed in the Jinja2 documentation. D3's own filters have their own pages, for example Text Filters and Array Filters.

Example 1: a filter without an argument

{{ "D3 Security" | length }}

Output:

11

Example 2: a filter with an argument

{% set alert = {"description": "Malware detected on endpoint."} %}

{{ alert.description | replace("Malware", "SUSPICIOUS ACTIVITY") }}

Output:

SUSPICIOUS ACTIVITY detected on endpoint.

Example 3: chaining filters

{% set alert = {
    "timestamps": [
        "2024-02-06T14:15:00",
        "2024-02-06T10:00:00",
        "2024-02-06T12:30:00",
        "2024-02-06T09:45:00",
        "2024-02-06T16:00:00"
    ]
} %}

{% set first_time = alert.timestamps | sort | first | replace("T", " ") %}
{% set last_time = alert.timestamps | sort | last | replace("T", " ") %}

First Seen: {{ first_time }}
Last Seen: {{ last_time }}

Output:

First Seen: 2024-02-06 09:45:00
Last Seen: 2024-02-06 16:00:00

sort puts the timestamps in order, first and last take the earliest and latest, and replace changes the T to a space.

Conditionals and Loops

  • If statements: {% if condition %} ... {% elif condition %} ... {% else %} ... {% endif %}

  • For loops: {% for variable in iterable %} ... {% endfor %}, over a list, a range, a dictionary or a string.

Conditional example 1: an elif branch is chosen

{% set severity = "Medium" %}

{% if severity == "High" %}
    Alert requires immediate attention.
{% elif severity == "Medium" %}
    Alert should be reviewed soon.
{% else %}
    No immediate action required.
{% endif %}

Output:

Alert should be reviewed soon.

Conditional example 2: nested conditions

{% set threat = {
    "severity": "Critical",
    "anomaly_detected": True,
    "attack_vector": "Ransomware"
} %}

{% if threat.severity == "Critical" %}
    Immediate escalation is required.
    {% if threat.anomaly_detected %}
        Anomaly confirmed. Engage the incident response team.
    {% else %}
        Anomaly not detected. Reassess threat data.
    {% endif %}
{% else %}
    No immediate action is required.
{% endif %}

Output:

Immediate escalation is required.

Anomaly confirmed. Engage the incident response team.

Conditional example 3: not

{% set firewall_enabled = False %}
{% if not firewall_enabled %}
    SOC Alert: Firewall is not enabled.
{% endif %}

Output:

SOC Alert: Firewall is not enabled.

Loop example 1: looping over a list

{% set ip_addresses = [
    "192.168.1.10",
    "10.0.0.5",
    "172.16.0.3",
    "203.0.113.25",
    "198.51.100.42",
    "192.168.2.15"
] %}

{# The allowed IP ranges are private networks: 192.168.x.x, 10.x.x.x, and 172.16.x.x. #}
{% for ip in ip_addresses %}
    {% if not (ip.startswith("192.168") or ip.startswith("10.") or ip.startswith("172.16")) %}
        External IP flagged: {{ ip }}
    {% endif %}
{% endfor %}

Output:

External IP flagged: 203.0.113.25

External IP flagged: 198.51.100.42

Text between {# and #} is a comment and is not shown in the output. Loop example 2: looping over nested lists

{% set file_hash = "a3f1c4b92e7d6f8098b2a1d5c3e4f7g601a2b3c4d5e6f7h8091a2b3c4d5e6f7a" %}
{% set segments = file_hash | batch(8, '') | list %}

{% for index in range(8) %}
    Segment {{ index + 1 }}: {{ segments[index] | join('') }}
{% endfor %}

Output:

Segment 1: a3f1c4b9

Segment 2: 2e7d6f80

Segment 3: 98b2a1d5

Segment 4: c3e4f7g6

Segment 5: 01a2b3c4

Segment 6: d5e6f7h8

Segment 7: 091a2b3c

Segment 8: 4d5e6f7a

batch(8, '') splits the string into chunks of 8 characters, list turns them into a list, range(8) gives the indexes 0 to 7, and join('') turns each chunk back into a string. Loop example 3: looping over a dynamic range

{% set ip_address = "192.168.1.100" %}
{% set octets = ip_address.split('.') %}

{% for index in range(octets | length) %}
    Octet {{ index + 1 }}: {{ octets[index] }}
{% endfor %}

Output:

Octet 1: 192

Octet 2: 168

Octet 3: 1

Octet 4: 100

split('.') gives the four octets, and range(octets | length) gives one index for each. Loop example 4: looping over a dictionary

{% set security_events = {
    "Unauthorized Access": "Critical",
    "Malware Infection": "High",
    "Suspicious Login": "Medium"
} %}

{% for event, severity in security_events.items() %}
    Event "{{ event }}" has a severity level of: {{ severity }}
{% endfor %}

Output:

Event "Unauthorized Access" has a severity level of: Critical

Event "Malware Infection" has a severity level of: High

Event "Suspicious Login" has a severity level of: Medium

Loop example 5: looping over a string

{% set filename = "malware$payload.exe" %}
{% set suspicious_chars = "!@#$%^&*" %}

{% for char in filename %}
    {% if char in suspicious_chars %}
        Alert: Suspicious character "{{ char }}" found in filename.
    {% endif %}
{% endfor %}

Output:

Alert: Suspicious character "$" found in filename.

Loop example 6 (advanced): a recursive loop that builds nested lists

{% set incident_list = [
    {"Incident": "Incident 1", "related_events": [
        {"Incident": "Incident 1.1"},
        {"Incident": "Incident 1.2", "related_events": [
            {"Incident": "Incident 1.2.1"},
            {"Incident": "Incident 1.2.2"}
        ]}
    ]},
    {"Incident": "Incident 2", "related_events": [
        {"Incident": "Incident 2.1"},
        {"Incident": "Incident 2.2"}
    ]}
] %}

{{ '[' }}
{% for object in incident_list recursive %}
    "{{ object.Incident }}"
    {% if object.related_events %}, [
        {{ loop(object.related_events) }}
    ]{% endif %}
    {% if not loop.last %}, {% endif %}
{% endfor %}
{{ ']' }}

Output:

[
  "Incident 1",

  [

    "Incident 1.1",

    "Incident 1.2",

    [

      "Incident 1.2.1",

      "Incident 1.2.2"

    ]

  ],

  "Incident 2",

  [

    "Incident 2.1",

    "Incident 2.2"

  ]

]

The recursive modifier lets the loop call itself on a sub-list with loop(...). {{ '[' }} outputs a bracket, and {% if not loop.last %}, {% endif %} adds a comma after every item except the last one at each level. Dictionary keys are case-sensitive.

Lists

Lists hold several values in square brackets ([]) and support indexing, slicing, looping, filtering and transformation.

Example 1: removing duplicates

{% set alerts = [
    "Failed Login from 192.168.1.10",
    "Malware Detected on Endpoint",
    "Failed Login from 192.168.1.10",
    "Unauthorized Access Attempt",
    "Malware Detected on Endpoint"
] %}

{% set unique_alerts = alerts | unique %}

Unique Security Alerts:
{% for alert in unique_alerts %}
    - {{ alert }}
{% endfor %}

Output:

Unique Security Alerts:

- Failed Login from 192.168.1.10

- Malware Detected on Endpoint

- Unauthorized Access Attempt

Example 2: combining lists

{% set list1 = ["malicious.com", "badactor.net"] %}
{% set list2 = ["phishingsite.org", "hacker.xyz"] %}

{% set combined = list1 + list2 %}

All Malicious Domains: {{ combined }}

Output:

All Malicious Domains: ['malicious.com', 'badactor.net', 'phishingsite.org', 'hacker.xyz']

Example 3: counting and keeping repeated values

{% set security_events = [
    {"username": "alice", "event": "Phishing Attempt"},
    {"username": "bob", "event": "Brute Force Attack"},
    {"username": "alice", "event": "Credential Stuffing"},
    {"username": "dave", "event": "DLP Violation"},
    {"username": "alice", "event": "Unauthorized Access Attempt"},
    {"username": "bob", "event": "Failed MFA Authentication"},
    {"username": "eve", "event": "Brute Force Attack"}
] %}

{# Step 1: Extract usernames from the list of events #}
{% set targeted_users = security_events | map('username') | list %}

{# Step 2: Count occurrences using a dictionary #}
{% set user_counts = {} %}
{% for user in targeted_users %}
    {% if user_counts[user] is not defined %}
        {% set _ = user_counts.update({user: 1}) %}
    {% else %}
        {% set _ = user_counts.update({user: user_counts[user] + 1}) %}
    {% endif %}
{% endfor %}

{# Step 3: Filter users who appear more than once #}
{% set repeated_targets = [] %}
{% for user, count in user_counts.items() %}
    {% if count > 1 %}
        {% set _ = repeated_targets.append(user) %}
    {% endif %}
{% endfor %}

{# Step 4: Output Users Who Were Targeted Multiple Times #}
Repeatedly Targeted Users:
{% for user in repeated_targets %}
    - {{ user }}
{% endfor %}

Output:

Repeatedly Targeted Users:

- alice

- bob

_ is a throwaway variable for a statement whose result is not needed. Example 4: filtering a list with selectattr

{% set security_events = [
    {"event": "Event1", "severity": "Critical"},
    {"event": "Event2", "severity": "Low"},
    {"event": "Event3", "severity": "Critical"},
    {"event": "Event4", "severity": "Informational"},
    {"event": "Event5", "severity": "High"},
    {"event": "Event6", "severity": "Medium"},
    {"event": "Event7", "severity": "High"},
    {"event": "Event8", "severity": "Critical"}
] %}

{% set high_critical_events = security_events | selectattr("severity", "in", ["High", "Critical"]) | list %}

High-Priority Security Events:
{% for event in high_critical_events %}
    - {{ event.event }} ({{ event.severity }})
{% endfor %}

Output:

High-Priority Security Events:

- Event1 (Critical)

- Event3 (Critical)

- Event5 (High)

- Event7 (High)

- Event8 (Critical)

Example 5: slicing

{% set ipv6_address = "2001:0db8:85a3:0000:0000:8a2e:0370:7334" %}
{% set segments = ipv6_address.split(':') %}

First 4 Segments: {{ segments[:4] | join(':') }}  
Last 4 Segments: {{ segments[-4:] | join(':') }}

Output:

First 4 Segments: 2001:0db8:85a3:0000
Last 4 Segments: 0000:8a2e:0370:7334

Slicing follows Python syntax. With events = ["A", "B", "C", "D", "E"]:

Notation

Result

Example

Output

list[start:end]

Elements from start to end-1

events[1:4]

['B', 'C', 'D']

list[:end]

From the beginning to end-1

events[:3]

['A', 'B', 'C']

list[start:]

From start to the end

events[2:]

['C', 'D', 'E']

list[-n:]

The last n elements

events[-3:]

['C', 'D', 'E']

list[:-n]

Without the last n elements

events[:-2]

['A', 'B', 'C']

list[start:end:step]

From start to end-1, by step

events[0:5:2]

['A', 'C', 'E']

list[::step]

All elements, by step

events[::2]

['A', 'C', 'E']

list[::-1]

The list reversed

events[::-1]

['E', 'D', 'C', 'B', 'A']

Macros (Functions)

Macros work like functions in other programming languages.

{%- and -%} trim the whitespace and line breaks before or after a tag. {% ... %} keeps them.

Example 1: hexadecimal to binary

{% macro hex_to_binary(hex_value) %}
    {%- set binary_str = [] -%}
    {%- for char in hex_value -%}
        {%- set binary_digit = "{:04b}".format(char | int(base=16)) -%}
        {%- set _ = binary_str.append(binary_digit) -%}
    {%- endfor -%}
    {{ binary_str | join(" ") }} 
{% endmacro %}

Binary Representation: {{ hex_to_binary("ABCD") }}

Output:

Binary Representation: 1010 1011 1100 1101

Example 2: detecting SQL injection

{% macro detect_sqli(request) %}
    {% set sqli_signatures = ["SELECT ", "UNION ", "DROP ", "--", "' OR '1'='1", "INSERT ", "UPDATE ", "DELETE "] %}
    {% set detected_signatures = [] %}

    {%- for sig in sqli_signatures -%}
        {%- if sig in request -%}
            {%- set _ = detected_signatures.append(sig) -%}
        {%- endif -%}
    {%- endfor -%}

    {%- if detected_signatures -%}
        {
            "alert": "SQL Injection Attempt Detected",
            "request": "{{ request }}",
            "matched_signatures": {{ detected_signatures | join(", ") }},
            "severity": "High"
        }
    {%- endif -%}
{% endmacro %}

SQLi Detection Output: {{ detect_sqli("GET /search?q=' OR '1'='1' UNION SELECT password FROM users") }}

Output:

SQLi Detection Output:

{
"alert": "SQL Injection Attempt Detected",
"request": "GET /search?q=' OR '1'='1' UNION SELECT password FROM users",
"matched_signatures": SELECT , UNION , ' OR '1'='1,
"severity": "High"
}

Example 3: detecting cross-site scripting (XSS)

{% macro detect_xss(input_data) %}
    {% set xss_signatures = ["<script", "javascript:", "onerror=", "onload=", "alert(", "document.cookie", "eval("] %}
    {% set detected_signatures = [] %}

    {%- for sig in xss_signatures -%}
        {%- if sig in input_data -%}
            {%- set _ = detected_signatures.append(sig) -%}
        {%- endif -%}
    {%- endfor -%}

    {%- if detected_signatures -%}
        {
            "alert": "Cross-Site Scripting (XSS) Attempt Detected",
            "input": "{{ input_data }}",
            "matched_signatures": {{ detected_signatures | join(", ") }},
            "severity": "High"
        }
    {%- endif -%}
{% endmacro %}

XSS Detection Output: {{ detect_xss('<script>alert("XSS")</script>') }}

Output:

XSS Detection Output:

{
"alert": "Cross-Site Scripting (XSS) Attempt Detected",
"input": "<script>alert("XSS")</script>",
"matched_signatures": <script, alert(,
"severity": "High"
}

Troubleshooting

When a Data Formatter task fails, its Status is Error, Return Data shows Failed, and the error message is on the Task Details tab.

A failed task's error

out-err.png

Error: can only concatenate str (not "int") to str

{% set num = 25 %}
{{ "The number is " + num }}

The task fails with can only concatenate str (not "int") to str.

Fix: convert the number to a string with the string filter.

{% set num = 25 %}
{{ "The number is " + num | string }}

Output:

The number is 25

Error: map() takes 3 positional arguments but 4 were given

{% set users = [{"name": "Elon"}, {"name": "Alice"}, {"name": "Emily"}] %}

{{ users | map("attribute", "name") | list }}

The task fails with map() takes 3 positional arguments but 4 were given.

Fix: loop over the list and append the attribute you need to a new list.

{% set users = [{"name": "Elon"}, {"name": "Alice"}, {"name": "Emily"}] %}

{% set result = [] %}
{% for user in users %}
    {% set _ = result.append(user.name) %}
{% endfor %}

{{ result | list }}

Output:

[
  "Elon",

  "Alice",

  "Emily"

]

Syntax Error: trunc (when writing a path)

{{ "C:\Users\name\Downloads" }}

The task fails with Syntax Error: trunc at line 1.

Fix: use double backslashes (\\).

{{ "C:\\Users\\name\\Downloads" }}

Output:

C:\Users\name\Downloads

Syntax Error: No filter named 'first.name'

{% set data = {"users": [{"name": "Alice"}, {"name": "Emily"}]} %}

First User: {{ data.users | first.name }}

The task fails with Syntax Error: No filter named 'first.name'. at line 3.

Fix: apply the filter, store the result in a variable, then read the attribute from the variable. You cannot read an attribute (.name) directly after a filter (first).

{% set data = {"users": [{"name": "Alice"}, {"name": "Emily"}]} %}

{% set first_user = data.users | first %}

First User: {{ first_user.name }}

Output:

First User: Alice

Syntax Error: expected token ':', got 'Unit'

{% set results = [] %}

{% set results = {
    Region: "demo_region",
    Business Unit: "demo_business_unit",
    Application ID: "demo_app_ID",
} %}

{{ results | tojson }}

The task fails with Syntax Error: expected token ':', got 'Unit' at line 5.

Fix: put dictionary keys in double quotes.

{% set results = [] %}

{% set results = {
    "Region": "demo_region",
    "Business Unit": "demo_business_unit",
    "Application ID": "demo_app_ID",
} %}

{{ results | tojson }}

Output:

{
  "Application ID": "demo_app_ID",

  "Business Unit": "demo_business_unit",

  "Region": "demo_region"

}