Useful Jinja Snippets

In this article, you will find several useful pieces of code that you might commonly use in Exponea, from simple personalization to more complicated ones used as jinja macros. You can simply copy-paste them into your own templates.

Display customer attribute with a fallback value

Every time you include some customer attribute in the text you need to take into consideration that not all customers have the particular attribute filled. For such cases, you can use a generic fallback value as illustrated below.

{% if customer.first_name %} {{customer.first_name}} {% else%}Sir/madam {% endif %}

If condition for dynamic content

{% if segmentations['segmentationId'] == "Group Name" %} 
your HTML content for subscribers from "Group Name" segment
{% else%} 
your HTML content for the rest of the customers
{% endif %}

Get email domain from email attribute

{{ customer.email.split('@') | last }}

Calculate the age of a customer

When customer.date_of_birth is a string in format 2017-01-31, calculated age has 1 year tolerance (does not count with months and days)

{% set age = time | from_timestamp('%Y') -  customer.date_of_birth.split('-')[0] %}

Don't execute an action for a specific customer

You can use the {% abort %} command which will stop executing code for a specific customer who meets the given conditions.

For example, using {% abort %} in an email node in a scenario will prevent the email from sending and the scenario will not continue to consequent nodes (for the specific customer). If used in an email, you will be able to see the abort runs in the scenarios test tab (when you hover over the email node).

No event is tracked for abort for now. However, if you use the syntax {% abort: "Custom message" %}, the action will not only be aborted, but it will also add a campaign event with status aborted and the Custom message will be in the property message. The custom message can be any Jinja expression, so you are not restricted to only constants. Therefore, you can, for instance, propagate the reason why the action was aborted for a particular customer. Note that while the abort tag is also available in non-actions (conditions, wait nodes, and limits), no event will be added even if the reason is provided.

Formatting UNIX timestamp after you added or deducted some time

{{ (time + 604800) | from_timestamp('%d-%m-%y')}}

Formating telephone number

Numbers are often tracked as integers, for instance, 9652508952. You can output them in the telephone number format, such as +7 (965) 250-89-52 using the following macro.

+7
(
{{ customer.mobile_phone[:3]}}
)
{{customer.mobile_phone[3:6]}}
-
{{customer.mobile_phone[6:8]}}
-
{{customer.mobile_phone[8:10]}}

📘

Changing the formating

If you are using different country code, just replace the 7 in the macro by your desired country code. Same for the hyphens, if you prefer to use spaces, just replace the hyphens with spaces.

Formatting price with custom thousand and decimal separators

{%- macro format_price(price, thousand_separator=',', decimal_separator='.', decimal_places=2) -%}
{%- set price_as_string = price | string -%}
{%- set price_split = price_as_string.split('.') -%}
{%- set price_integer = price_split[0] -%}
{%- if price_split | count > 1 -%}
{%- set price_fraction = price_split[1] -%}
{%- if price_fraction | length < decimal_places -%}
{%- set price_fraction = price_fraction.ljust(decimal_places, '0') -%} 
{%- else -%}
{%- set price_fraction = price_fraction[:decimal_places] -%}
{%- endif -%}
{%- else -%}
{%- set price_fraction = '' -%}
{%- endif -%}
{%- set formatted_price_integer = price_integer | reverse | batch(3) | map('join', '') | join(thousand_separator) | reverse -%}
{%- if price_fraction != '' -%}
{%- set formatted_price = formatted_price_integer ~ decimal_separator ~ price_fraction -%}
{%- else -%}
{%- set formatted_price = formatted_price_integer-%}
{%- endif -%}
{{- formatted_price -}} {%- endmacro -%}

Sample output:
{{format_price(1.2) }} =>1.20 (2 decimal places) 
{{format_price(1234.5) }} => 1,234.50 (comma and 2 decimal places) {{format_price(1234) }} =>1,234 (just comma)

Rounding number down with 1 decimal number precision

{{ 42.55 | round(1, 'floor') }} => 42.5

Random dynamic wait time

Spread send over 3 hours:
{{ range(0,180) | random }}

Create sixty chunks 10 minutes apart:
{{ (range(0,60) | random)*10 }}

Wait time in business days

In this example, the Jinja will set the wait time to be 9 business days, however you can change this by replacing the number 9 in the part {%for counter in range (0,9) %} with a desired number of days.

{% set calculated_time = time %}{%for counter in range (0,9) %}
{% set calculated_time = calculated_time + 86400%}
{% if (calculated_time|from_timestamp('%w'))| int == 6%}{% set calculated_time = calculated_time + 172800 %}
{% elif (calculated_time|from_timestamp('%w'))| int == 0%}{% set calculated_time = calculated_time +86400 %}{%endif%}
{%if loop.last%}{{(calculated_time - time)/86400}}{%endif%}{%endfor%}

Wait 2 days before departure

Calculate timestamp 2 days before departure_time:
{{ event.departure_time - 2 * 86400 }}

Calculate time difference (time to wait from now):
{{ event.departure_time - 2 * 86400 - time }}

Calculate time difference in hours (to use in Wait node in scenarios):
We have an event with departure_time in the future. We want send email 2 days before departure
{{ (event.departure_time - 2 * 86400 - time) / 3600 }}

Selecting items from the catalog

{# Select one item using its ID #}
{% set item = catalogs.myCatalog.item_by_id('item_id_1') %}

{# Selecting multiple items using a list of IDs #}
{% set items = catalogs.myCatalog.items_by_id(['item_id_1', 'item_id_2', 'item_id_3', ...]) %}

Output multiple catalog values for a list of item IDs

This is commonly used in cart abandonment emails when you have a list of items taken from the last cart update of the customer as an aggregate. If you work with JSON and not a simple list, you need to further specify the product when setting the item.

{% set list_of_ids = aggregates['5ee8d09fce3f22fcfe512463'] %}
{% set items = catalogs['catalog name'].items_by_id(list_of_ids) %}
{% for item in items %}
  {{ item['name'] }}, {{item['price']}} 
  <img src="{{item['image_source']}}">; 
{% endfor %}

Get all items from a report

{% set x = reports[reportId].rows | sort(attribute=attrNumber, reverse=True) %}
actionToDoWithItem("{{ x[itemNumber][attrNumber] }}");

Get some items from the report and access a catalog

This code takes 2nd-4th item (because the other is present in the report) and looks it up in the catalog based on the first column (item_id). A possible way to show best selling products, etc. without recommendations.

{% set report_list = reports['Report ID'].rows | sort(attribute=1, reverse=True) %}
{% for item in report_list %}
  {% if (not loop.first) and (loop.index < 5) %}
    {% set product = catalogs['Catalog name'].item_by_id(item[0]) %}
    {{ item[0] }} = {{ product.name }}<be>
  {% endif%}
{%endfor%}

Multiple IDs for webhooks (for REST API request)

"customer_ids": {
        {% if customer_ids.registered %}
            "registered": {{ customer_ids.registered | json }}
        {% elif customer_ids.email_id %}
            "email_id": {{ customer_ids.email_id | json if customer_ids.email_id is string else customer_ids.
email_id | first | json }}
        {% elif customer_ids.phone_id %}
            "phone_id": {{ customer_ids.phone_id | json if customer_ids.phone_id is string else customer_ids.
phone_id | first | json }}
        {% else %}
            "cookie": {{ customer_ids.cookie | json if customer_ids.cookie is string else customer_ids.cookie |
first | json }}
{% endif %} 
}

Lowercase email_id (in REST API request)

"email_id": {{ customer.email | lower | json }}

If doing JSON API requests, it may actually make more sense to build the request as a dictionary in jinja and serialize the whole thing.

{{ {"customer": {"email_id": customer.email | lower}, "other_key": 42} | json }}

Selecting the first cookie of a customer

{{ customer_ids.cookie if customer_ids.cookie is string else customer_ids.cookie | first }}

Convert string from JSON

item.str | from_json

Hashing emails with key and time DDYYYY

{{ (customer.email ~ 'some secret key' ~ (time | from_timestamp('%m')) ~ (time | from_timestamp('%Y'))) | hash
('sha1') | b64encode }}

Getting gender for SK/CZ

Set attribute: gender = {{ "female" if (customer.last_name | reverse)[:3] | reverse | replace('á', 'a') == "ova"
else "male" }}

Delete duplicates from multiple arrays

{% set reco_1 = recommendations('59df6b8bfb60098bb6f21188', 10) %}
{% set reco_2 =  recommendations('59e0cc81fb600959f3898f53', 10) %}
{% set all_item_ids = [] %}
{% for item in reco_1 %}
    {% append item.item_id to all_item_ids %}
{% endfor %}
{% for item in reco_2 %}
    {% append item.item_id to all_item_ids %}
{% endfor %}
{% set unique_item_ids = (all_item_ids | unique) %}
{% set final = [] %}
{% for item in unique_item_ids %}
        {% set item_final = catalogs.products.item_by_id(item) %}
        {% append item_final to final %}
{% endfor%}
{% for item in final %}
...
{% endfor %}

Detect if registered identity is not a lowercase email

{{ customer_ids.registered == (customer.email | lower) }}

Count cookies

cookie_cnt = {{ (1 if customer_ids.cookie is string else customer_ids.cookie | length) if customer_ids.cookie else 0 }}

Optimal sending time based on "On Event" trigger

{% set hour = event.timestamp | from_timestamp('%-H') | int %} {% set minute = event.timestamp | from_timestamp('%-M') | int %} {% set minutes = hour*60 + minute | int %}
{% set optimal = customer.hour * 60 | int %}
{% if optimal < minutes %}
{{ 1440 - minutes + optimal }}
{% elif optimal> minutes %}
{{ optimal - minutes }}
{% else %}
0
{% endif %}

Parsing item_id from object in cart_update

{% for product in aggregates['AGGREGATE_ID'] %}
{% set item = catalogs.CATALOG_NAME.item_by_id(product.VARIANT_ID) %}
<img src="{{ item.image_url }}">
{{ item.product_title }}<br>
{{ item.price }}
{% endfor %}

Adjusting time from UTC according to winter/summer time

{% set timestamp = time %}
{% set month = timestamp | from_timestamp('%-m') | int %}
{% set weekday = timestamp | from_timestamp('%w') | int %}
{% set dayofmonth = timestamp | from_timestamp('%-d') | int %}
{% if month < 3 or (month == 3 and dayofmonth < 25) %}
        {% set timezone_difference = 3600 %}
{% elif (month == 3 and weekday < (dayofmonth - 24)) %}
        {% set timezone_difference = 7200 | int %}
{% elif month == 3 %}
        {% set timezone_difference = 3600 | int %}
{% elif month < 10 %}
        {% set timezone_difference = 7200 | int %}
{% elif (month == 10 and weekday < (dayofmonth - 24)) %}
        {% set timezone_difference = 3600 | int %}
{% elif month == 10 %}
        {% set timezone_difference = 7200 | int %}
{% else %}
        {% set timezone_difference = 3600 | int %}
{% endif %}

Hashing a string

To get the sha1 hash of a string:
{{ 'test1'|hash('sha1') }}

To get the md5 hash of a string:
{{ 'test1'|hash('md5') }}

Generate random ulong number

{%- set requestId = 0 -%}
{%- for index in range(0, 8) -%}
    {%- set randomByte = range(0, 256) | random -%}
    {%- set requestId = requestId * 256 + randomByte -%}
    {%- if loop.last -%}
        {{- requestId -}}
    {%- endif -%}
{%- endfor -%}

Removing items from list

To remove an item from a List, use the list.pop() method. In case you do not know the index of the item to remove, search for it using list.index().
Example:

{% set x = [10, 20, 30] %}
{% set indx = x.index(20) %}
{% set dummyVar = x.pop(indx) %}
{{ x }}

Prints:
[10, 20]

Get mode (item with the highest occurrence in a list)

  1. Set arguments:
    list_to_count (required) - List of items whose values should be counted
    sorting_type (optional) - Whether the resulting list should be ordered ascending or descending or not ordered
    size (optional) - Amount of items that should be in the returned list
  2. Use the value in unique_values_count.
{#- Set variables here -#}
{%- set list_to_count = [3,5,5,7,3,5,8,5] -%} {#- List whose mode occurences should be computed -#}
{%- set sorting_type = 'asc' -%} {#- 'asc'/'desc' or other for unsorted -#}
{%- set size = 3 -%} {#- Number of items to return. All items if size not number or size < 0 -#}
{#- For each unique value, count occurences in the list and push it to 'unique_values_count' -#}
{#- as {count: NumOfOccurences, value: valueInTheList} -#}
{%- set unique_values = list_to_count | unique -%}
{%- set unique_values_count = [] -%}
{%- for value in unique_values -%}
        {%- append {"value": value, "count": list_to_count.count(value)} to unique_values_count -%}
{%- endfor -%}
{#- Sort the counted occurrences if specified -#}
{%- if sorting_type == 'desc' -%}
        {%- set reverse = True -%}
{%- elif sorting_type == 'asc' -%}
        {%- set reverse = False -%}
{%- else -%}
        {%- set reverse = None -%}
{%- endif -%}
{%- if reverse is not none -%}
        {%- set unique_values_count = unique_values_count | sort(attribute="count", reverse=reverse) | list -%}
{%- endif -%}
{#- Return first X items from the sorted list if specified -#}
{%- if unique_values_count | length > 0 -%}
        {%- if (size is number) and (size >= 0) -%}
                {%- set unique_values_count = unique_values_count | batch(size) | list -%}
                {%- set unique_values_count = unique_values_count[0] -%}
        {%- endif -%}
{%- endif -%}
{{- unique_values_count -}} // To work with the whole array
{{- unique_values_count[0] -}} // To get the first item, e.g. {'count': 12, value: 'Hello'}
{{- unique_values_count[0].value -}} // To get the value of first item, e.g. 'Hello'
{{- unique_values_count[0].count -}} // To get the occurrences of the first item, e.g. 12

Phone number without symbols, spaces or leading zeros

A Jinja filter to remove symbols, spaces and leading zeros from any phone number format; the output is the phone number in a raw format.
You might need to change the customer attribute name to match yours.

{{ customer.phone | replace(' ','') | replace('+','') | replace('(','')| replace(')','') | replace('-','') | int }}

Power Jinja

Power Jinja includes a lot of more advanced functionality which is then translated into code that is compatible with our implementation. However, this comes at a performance and stability cost. Hence, before using Power Jinja, you need to consider the pros and cons listed in the table below

Pros

Cons

Ad-hoc expansion of jinja functionalities without the need to think about basic data manipulation within jinja.

Power Jinja makes use of nested macro calls. Hence, it can be up to 2-4 times slower than just pasting the bodies of the macros within your file, when you are chaining multiple macros.

Chainability of the macros to apply multiple transformations to data in a single step.

Whenever you include a new Power Jinja macro, then your file has to be processed again with the Power Jinja website in order to include the definition of the new macro in text.

Power Jinja supports conditional loops.

Since conditional loops are possible, this can lead to infinite loops.

Using Power Jinja macros can help identify what functionalities are used the most in jinja (simply by searching for the macro names), so these could be integrated into the product.

Given the pros and cons, this may be a good solution for when there are few or no additional macros chained up. Because of the performance If you need to do more complex data manipulation in jinja, consider how many customers the template needs to be rendered for.

If you have decided that the drop in performance is worth it, using Power Jinja is quite simple. Firstly, write your code at the Power Jinja website using the macros. Then, optionally check the Minify output to minify the jinja that will be inserted into your text, and compile the text by pressing the Powerjinjize! button. Lastly, just paste the output code into your templates.

📘

Compatibility of append in Power Jinja

Make sure that the Append command is set to append item to list for your code to be compatible with our implementation.

Updated about a month ago

Useful Jinja Snippets


Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.


We rely on cookies

to optimize our communication and to enhance your customer experience. By clicking on the Accept and Close button, you agree to the collection of cookies. You can also adjust your preferences by clicking on Manage Preferences. For more information please see our Privacy policy.

Manage cookies
Accept & close

Cookies preferences

Accept & close
Back