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
- Jinja2 Template Designer Docs (palletsprojects.com)
- Jinja2 API Documentation (palletsprojects.com)
- Flask Integration (flask.palletsprojects.com)