Flask Jinja2 Basics
Jinja2 is the templating engine Flask uses to build HTML pages. It lets you mix Python variables and logic directly into your HTML files. The browser receives pure HTML — Jinja2 runs on the server and is invisible to the user.
The Mail-Merge Analogy
Jinja2 works exactly like a mail-merge in a word processor. You write one letter template with placeholders like Dear [Name]. The system fills in each person's name before printing. Jinja2 fills in Python variables before sending HTML to the browser.
Template: <h1>Hello, {{ username }}!</h1>
Data: username = 'Alice'
Output: <h1>Hello, Alice!</h1>
Rendering a Template
Create a file at templates/index.html:
<!DOCTYPE html>
<html>
<head><title>Home</title></head>
<body>
<h1>Hello, {{ username }}!</h1>
<p>You have {{ message_count }} new messages.</p>
</body>
</html>In your Python route, call render_template() and pass the variables:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def home():
return render_template('index.html', username='Alice', message_count=5)Flask reads templates/index.html, substitutes {{ username }} with Alice and {{ message_count }} with 5, and sends the resulting HTML to the browser.
Jinja2 Syntax: Three Building Blocks
| Syntax | Purpose | Example |
|---|---|---|
{{ }} | Output a variable or expression | {{ username }} |
{% %} | Logic: if, for, block, extends | {% if user %} |
{# #} | Comments (not sent to browser) | {# This is a note #} |
If Statements in Templates
{% if user_logged_in %}
<p>Welcome back, {{ username }}!</p>
{% else %}
<p>Please <a href="/login">log in</a>.</p>
{% endif %}Every {% if %} block must close with {% endif %}. The {% else %} is optional.
For Loops in Templates
<ul>
{% for product in products %}
<li>{{ product.name }} — ${{ product.price }}</li>
{% endfor %}
</ul>Python route that passes the list:
@app.route('/products')
def products():
items = [
{'name': 'Laptop', 'price': 999},
{'name': 'Mouse', 'price': 25},
{'name': 'Keyboard', 'price': 49}
]
return render_template('products.html', products=items)Jinja2 loops through the list and renders one <li> for each item.
The loop Variable
Inside a for loop, Jinja2 provides a special loop object with useful properties:
| Property | Value |
|---|---|
loop.index | Current iteration (starts at 1) |
loop.index0 | Current iteration (starts at 0) |
loop.first | True on the first iteration |
loop.last | True on the last iteration |
loop.length | Total number of items |
{% for item in items %}
<p>Item {{ loop.index }} of {{ loop.length }}: {{ item }}</p>
{% endfor %}Accessing Object Properties and Dictionary Keys
Jinja2 uses dot notation for both object attributes and dictionary keys interchangeably:
{# For a dictionary: {'name': 'Alice'} #}
{{ user.name }}
{# For an object with .name attribute #}
{{ user.name }}
{# Both work the same way in Jinja2 #}Expressions and Math
Jinja2 evaluates basic Python expressions inside {{ }}:
<p>Total: ${{ price * quantity }}</p>
<p>Discount: {{ "10% off" if member else "No discount" }}</p>Escaping HTML
Jinja2 automatically escapes HTML characters in variables for security. If a variable contains <script>alert('hi')</script>, Jinja2 renders it as visible text, not executable code. This prevents cross-site scripting (XSS) attacks by default.
To render raw HTML intentionally, use the safe filter:
{{ content | safe }}Only use | safe on content you fully control — never on user input.
Summary
Jinja2 templates separate your HTML structure from your Python data. Double curly braces output values. Percent-sign blocks handle logic like loops and conditions. Flask renders templates by merging Python data with HTML on the server before sending the result to the browser. This pattern keeps your code organized and your HTML reusable.
