Useful Jinja Macros

Several useful pieces of code are commonly used in Exponea. You can find them here and just copy-paste them into your templates. The more complicated ones are already given as macros. However, you can make the others into macros too. For reference on how to use macros go to Jinja blocks document.

Converting to telephone number format

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') -%} {%- 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)

Random dynamic wait time

{{ range(0,180) | random }}
for longer time periods
{{ (range(0,180) | random)*10 }} 
(which would create 10 minute chunks).

Selecting the first item from the list

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

Selecting multiple items from the catalogue

{{ catalogs.myCatalog.items_by_id(['item_id_1', 'item_id_2', 'item_id_3', ...]) }}

Multiple Ids webhook (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 }}

Convert string from JSON

item.str | from_json

Calculate the age of a customer

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

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 event with departure_time in the future. We want send email 2 days before departure
{{ (event.departure_time - 2 * 86400 - time) / 3600 }}

Hashing emails with key and time DDYYYY

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

Formatting UNIX timestamp after you added or deducted some time

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

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

Get all items from a report

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

Detect if registered identity is not a lowercase email

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

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') }}
Get a string checksum:
{{ 'test2'|checksum }}
Other hashes (platform dependent):
{{ 'test2'|hash('blowfish') }}
To get a sha512 password hash (random salt):
{{ 'passwordsaresecret'|password_hash('sha512') }}
To get a sha256 password hash with a specific salt:
{{ 'secretpassword'|password_hash('sha256', 'mysecretsalt') }}

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

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

Power Jinja

Some additional useful macros can be found at PowerJijna. 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 from Power Jinja Documentation. 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.

Useful Jinja Macros


Suggested Edits are limited on API Reference Pages

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