Useful Jinja Snippets

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

Display price with 2 decimal places

If your prices are displaying with only one decimal place, you can use the below Jinja to force 2 decimal places.

{{ "%0.2f" | format(item.price | float) }}

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] %}

📘

Timestamp value in seconds

The value for a timestamp should always be in seconds since there is no automatic recognition of milliseconds based on character number.

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

Please bear in mind that the output values are in seconds.

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

Output time in the correct timezone

{% set timezone = 'Europe/Bratislava' %}
{% set offset = (time - time | from_timestamp('%x %X %f') | to_timestamp('%x %X %f', timezone=timezone)) | round %}
{% set calculated_time = time + offset %}
{{ calculated_time | from_timestamp('%x %X') }}

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, 30]

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

Accessing information from the catalogs

Instead of ineffectively using, for example, five different calls to the catalog, use just one.

{%- set cart_item_ids = ['id_1','id_2','id_3','id_4','id_5'] -%}
{%- set items = [] -%}
{%- set catalog_items = catalogs['cat_name'].items_by_id(cart_item_ids) -%}	

{%- for item in catalog_items if item['active'] in [true, 'True', 'yes'] -%}
	{%- append item to items -%}
{%- endfor -%}

Moreover, you might need to reference the item_id in the loop. For example, to check if the item_id is not in the list of excluded items. You can set item_id in the loop when calling the catalog:

{%- set excluded_product_ids = ['id_3','id_4','id_5'] -%}
{%- set cart_item_ids = ['id_1','id_2','id_3','id_4','id_5'] -%}
{%- set items = [] -%}
{%- set catalog_items = catalogs['cat_name'].items_by_id(cart_item_ids) -%}	

{%- for item in catalog_items if item['active'] in [true, 'True', 'yes'] -%}
	{%- set item_id = cart_item_ids[loop.index0] -%}
    {%- if item_id not in excluded_product_ids -%}
	    {%- append item to items -%}
    {%- endif -%}
{%- endfor -%}

Otherwise, using more calls may prolong the email rendering resulting in a template rendering timeout error.

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 }}