NexusCS

Jinja2

Python
Jinja2 template engine for Python. Used in Flask, Ansible, and many other frameworks.

Getting started

Basic syntax

{# Comment #}
{{ variable }}
{% statement %}

Three delimiter types.

Variables

{{ user.name }}
{{ user['name'] }}
{{ items[0] }}

Dot or subscript notation.

Filters

{{ name|upper }}
{{ text|default("N/A") }}
{{ list|join(", ") }}

Transform values with |.

Delimiters

Types

Delimiter Purpose Example
{{ }} Print variable {{ user.name }}
{% %} Statements {% if %}
{# #} Comments {# Note #}

Examples

{{ variable }}
{% for item in items %}
{# This is a comment #}

Filters

String filters

{{ name|upper }}
{{ name|lower }}
{{ name|title }}
{{ name|capitalize }}
{{ text|trim }}
{{ text|striptags }}
{{ text|truncate(20) }}
{{ name|replace("a", "b") }}

List filters

{{ list|join(", ") }}
{{ list|first }}
{{ list|last }}
{{ list|length }}
{{ list|reverse }}
{{ list|sort }}
{{ list|unique }}

Other filters

{{ var|default("fallback") }}
{{ var|d("fallback") }}
{{ html|safe }}
{{ input|escape }}
{{ input|e }}
{{ num|abs }}
{{ num|round }}
{{ num|round(2) }}

Tests

Common tests

{% if var is defined %}
{% if var is undefined %}
{% if var is none %}
{% if val is odd %}
{% if val is even %}
{% if val is divisibleby(3) %}
{% if text is string %}
{% if num is number %}

With negation

{% if var is not none %}
{% if not var is defined %}

Control flow

If/elif/else

{% if user.active %}
  Active
{% elif user.pending %}
  Pending
{% else %}
  Inactive
{% endif %}

For loops

{% for item in items %}
  {{ item }}
{% endfor %}

{% for item in items %}
  {{ item }}
{% else %}
  No items
{% endfor %}

{% for user in users if not user.hidden %}
  {{ user.name }}
{% endfor %}

Unpacking

{% for key, value in dict.items() %}
  {{ key }}: {{ value }}
{% endfor %}

Loop variables

Available variables

Variable Description
loop.index Current iteration (1-indexed)
loop.index0 Current iteration (0-indexed)
loop.revindex Iterations from end
loop.first True on first iteration
loop.last True on last iteration
loop.length Total items
loop.cycle() Cycle through values
loop.previtem Previous item
loop.nextitem Next item

Examples

{% for item in items %}
  {{ loop.index }}: {{ item }}
  {% if loop.first %}First!{% endif %}
  {% if loop.last %}Last!{% endif %}
{% endfor %}

Template inheritance

Base template

{# base.html #}
<!DOCTYPE html>
<html>
<head>
  {% block head %}
  <title>{% block title %}{% endblock %}</title>
  {% endblock %}
</head>
<body>
  {% block content %}{% endblock %}
  {% block footer %}
  <footer>© 2024</footer>
  {% endblock %}
</body>
</html>

Child template

{% extends "base.html" %}

{% block title %}Home{% endblock %}

{% block content %}
  <h1>Welcome</h1>
{% endblock %}

Super blocks

{% block head %}
  {{ super() }}
  <link rel="stylesheet" href="custom.css">
{% endblock %}

Include parent block content.

Macros

Defining macros

{% macro input(name, value='', type='text') %}
  <input type="{{ type }}"
         name="{{ name }}"
         value="{{ value|e }}">
{% endmacro %}

{% macro button(text, type='submit') %}
  <button type="{{ type }}">{{ text }}</button>
{% endmacro %}

Calling macros

{{ input('username') }}
{{ input('password', type='password') }}
{{ button('Submit') }}

Importing macros

{% import 'forms.html' as forms %}
{{ forms.input('email') }}

{% from 'forms.html' import input, button %}
{{ input('username') }}

Include

Basic include

{% include 'header.html' %}
Content here
{% include 'footer.html' %}

With context control

{% include 'sidebar.html' without context %}
{% include 'sidebar.html' with context %}

Ignore missing

{% include 'optional.html' ignore missing %}

{% include ['custom.html', 'default.html'] %}

Whitespace control

Strip whitespace

{%- if true %}
  content
{% endif %}

{% if true -%}
  content
{% endif %}

{%- if true -%}
  content
{%- endif -%}

{{- variable -}}

⚠️ No space between tag and -!

Prevent stripping

{%+ if something %}yay{% endif %}
{% if something +%}yay{% endif %}

Assignments

Set variables

{% set name = 'John' %}
{% set x, y = 10, 20 %}
{% set items = [1, 2, 3] %}

Block assignments

{% set navigation %}
  <li><a href="/">Home</a></li>
  <li><a href="/about">About</a></li>
{% endset %}

{% set reply | wordwrap %}
  You wrote: {{ message }}
{% endset %}

Namespace (scope workaround)

{% set ns = namespace(found=false) %}
{% for item in items %}
  {% if item.check() %}
    {% set ns.found = true %}
  {% endif %}
{% endfor %}
Found: {{ ns.found }}

Expressions

Math operators

{{ 1 + 1 }}        {# 2 #}
{{ 5 - 3 }}        {# 2 #}
{{ 4 * 3 }}        {# 12 #}
{{ 10 / 3 }}       {# 3.333 #}
{{ 10 // 3 }}      {# 3 #}
{{ 10 % 3 }}       {# 1 #}
{{ 2 ** 3 }}       {# 8 #}

Comparisons

{{ 1 == 1 }}       {# true #}
{{ 1 != 2 }}       {# true #}
{{ 5 > 3 }}        {# true #}
{{ 5 >= 5 }}       {# true #}
{{ 2 < 5 }}        {# true #}
{{ 2 <= 2 }}       {# true #}

Logic

{{ true and false }}
{{ true or false }}
{{ not true }}

String concat

{{ "Hello " ~ name ~ "!" }}

Use tilde ~ operator.

In operator

{% if 1 in [1, 2, 3] %}
{% if 'key' in dict %}

Inline if

{{ 'yes' if condition else 'no' }}
{{ value if value is defined }}

Escaping

Raw blocks

{% raw %}
  {% for item in seq %}
    <li>{{ item }}</li>
  {% endfor %}
{% endraw %}

No processing inside.

HTML escaping

{{ user_input|escape }}
{{ user_input|e }}
{{ trusted_html|safe }}

⚠️ Always escape user input!

Gotchas

Pitfalls

Issue Solution
Variable scope Use namespace() objects
Undefined vars Check with is defined
Whitespace No space before -
Autoescaping Use |e filter
Extends placement Must be first tag
No break/continue Use inline filtering

Power operator

{# Jinja: left to right #}
{{ 3**3**3 }}  {# (3**3)**3 #}

{# Python: right to left #}
{# 3**3**3 = 3**(3**3) #}

Use parentheses for clarity!

Also see