Skip to content

Jinja2 Delimiters

Jinja2 uses special delimiters to embed logic into HTML.

Use this to output variables or expressions:

html
<p>User: {{ username }}</p>
<p>2 + 2 = {{ 2 + 2 }}</p>

Use this for control flow (if/for), blocks, macros:

html
{% if is_logged_in %}
  <p>Welcome back!</p>
{% else %}
  <p>Please log in.</p>
{% endif %}

These do not appear in final HTML:

html
{# This is a comment #}

If you accidentally use {{ ... }} for an if statement, you’ll get confusing output/errors.

Remember:

  • {{ }} outputs
  • {% %} controls

Jinja2 has exactly three pieces of syntax, and mixing them up is the most common template error:

diagram Diagram mermaid
one template, all three
A{{ 1+1 }} B{% for i in [1,2] %}{{ i }}{% endfor %} C{# hidden #}D

renders to exactly:

measured output
A2 B12 CD

The comment left nothing behind — not even whitespace — and the {% for %} produced output only through the {{ i }} inside it. A common mistake is writing {% user.name %}, which is a statement tag containing an expression and raises; you want {{ user.name }}.

This is the single most important thing on this page. Flask’s rule is:

flask/app.py
return filename.endswith((".html", ".htm", ".xml", ".xhtml", ".svg"))

Measured:

template nameautoescaped?
page.htmlyes
page.svgyes
page.xmlyes
page.txtno
report.csvno
page.j2no
index.html.j2no

With escaping on, hostile input is neutralised:

measured, v = <script>alert(1)</script>
{{ v }}        ->  &lt;script&gt;alert(1)&lt;/script&gt;
{{ v|safe }}   ->  <script>alert(1)</script>      <- injected verbatim
{{ v|e }}      ->  &lt;script&gt;alert(1)&lt;/script&gt;

|safe means “I promise this is trusted HTML”. Applying it to anything derived from a request is how XSS happens.

without the minus
{% for i in [1,2] %}
  {{ i }}
{% endfor %}          ->  '\n  1\n\n  2\n'
with it
{%- for i in [1,2] %}
  {{ i }}
{%- endfor %}         ->  '\n  1\n  2'

A - on either side of a tag strips adjacent whitespace. It matters for anything whitespace-sensitive — YAML, CSV, plain-text email — and is cosmetic for HTML.

sketch Which delimiter does what p5.js
Expressions print, statements control flow, comments vanish. Autoescaping depends on the template's file extension.
pch.quizTag pch.quizDefaultTitle
  1. Is a template named index.html.j2 autoescaped by Flask?

    pch.quizShowAnswer

    B — no, because Flask checks whether the name ENDS with .html, .htm, .xml, .xhtml or .svg — Measured False. The very common name.html.j2 convention silently disables escaping, making every {{ user_input }} an XSS hole. Name templates .html or configure select_autoescape yourself.

  2. What does {# note #} contribute to the rendered output?

    pch.quizShowAnswer

    B — nothing at all; it is removed before rendering — Measured: 'C{# hidden #}D' rendered as 'CD'. Unlike an HTML comment, a Jinja comment never reaches the browser, so it is safe for notes you do not want shipped.

  3. With autoescaping on and v = <script>alert(1)</script>, what does {{ v|safe }} render?

    pch.quizShowAnswer

    C — the script tag verbatim, which the browser executes — |safe means 'this is trusted HTML, do not escape it'. Applying it to anything derived from a request is precisely how XSS happens.

pch.coffeeTagline

pch.coffeeCta

pch.feedbackHeading

pch.feedbackSubheading