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

SyntaxPurposeExample
{{ }}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:

PropertyValue
loop.indexCurrent iteration (starts at 1)
loop.index0Current iteration (starts at 0)
loop.firstTrue on the first iteration
loop.lastTrue on the last iteration
loop.lengthTotal 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.

Leave a Comment

Your email address will not be published. Required fields are marked *