Jinja2 Delimiters
Jinja2 uses special delimiters to embed logic into HTML.
1) Print an expression: {{ ... }}
Section titled “1) Print an expression: {{ ... }}”Use this to output variables or expressions:
<p>User: {{ username }}</p>
<p>2 + 2 = {{ 2 + 2 }}</p>2) Template statements: {% ... %}
Section titled “2) Template statements: {% ... %}”Use this for control flow (if/for), blocks, macros:
{% if is_logged_in %}
<p>Welcome back!</p>
{% else %}
<p>Please log in.</p>
{% endif %}3) Comments: {# ... #}
Section titled “3) Comments: {# ... #}”These do not appear in final HTML:
{# This is a comment #}Common pitfall
Section titled “Common pitfall”If you accidentally use {{ ... }} for an if statement, you’ll get confusing output/errors.
Remember:
{{ }}outputs{% %}controls
Three delimiters, three jobs
Section titled “Three delimiters, three jobs”Jinja2 has exactly three pieces of syntax, and mixing them up is the most common template error:
flowchart LR
E["{{ ... }}
EXPRESSION
prints a value"] --> O1["output appears"]
S["{% ... %}
STATEMENT
if, for, block, set"] --> O2["controls flow
prints nothing itself"]
C["{# ... #}
COMMENT
removed entirely"] --> O3["never reaches the browser"]
A{{ 1+1 }} B{% for i in [1,2] %}{{ i }}{% endfor %} C{# hidden #}Drenders to exactly:
A2 B12 CDThe 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 }}.
Autoescaping is decided by the file name
Section titled “Autoescaping is decided by the file name”This is the single most important thing on this page. Flask’s rule is:
return filename.endswith((".html", ".htm", ".xml", ".xhtml", ".svg"))Measured:
| template name | autoescaped? |
|---|---|
page.html | yes |
page.svg | yes |
page.xml | yes |
page.txt | no |
report.csv | no |
page.j2 | no |
index.html.j2 | no |
With escaping on, hostile input is neutralised:
{{ v }} -> <script>alert(1)</script>
{{ v|safe }} -> <script>alert(1)</script> <- injected verbatim
{{ v|e }} -> <script>alert(1)</script>|safe means “I promise this is trusted HTML”. Applying it to anything derived from a
request is how XSS happens.
Whitespace control
Section titled “Whitespace control”{% for i in [1,2] %}
{{ i }}
{% endfor %} -> '\n 1\n\n 2\n'{%- 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.
See it move
Section titled “See it move”Check yourself
Section titled “Check yourself”-
Is a template named index.html.j2 autoescaped by Flask?
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.
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.
-
What does {# note #} contribute to the rendered output?
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.
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.
-
With autoescaping on and v = <script>alert(1)</script>, what does {{ v|safe }} render?
|safe means 'this is trusted HTML, do not escape it'. Applying it to anything derived from a request is precisely how XSS happens.
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.coffeeCtapch.feedbackHeading
pch.feedbackSubheading