Skip to main content

Overview

Flask uses Jinja2 as its template engine, providing powerful template rendering with automatic escaping, inheritance, and integration with Flask’s context system.

Template Basics

Directory Structure

By default, Flask looks for templates in the templates folder:
your_application/
├── app.py
└── templates/
    ├── base.html
    ├── index.html
    └── user/
        └── profile.html

Rendering Templates

From templating.py:136-161, use render_template() to render templates:
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/user/<username>')
def profile(username):
    user = get_user(username)
    return render_template('user/profile.html', user=user)

Jinja2 Environment

Flask creates a customized Jinja2 environment (from app.py:469-507):
def create_jinja_environment(self):
    options = dict(self.jinja_options)
    
    if "autoescape" not in options:
        options["autoescape"] = self.select_jinja_autoescape
    
    if "auto_reload" not in options:
        auto_reload = self.config["TEMPLATES_AUTO_RELOAD"]
        if auto_reload is None:
            auto_reload = self.debug
        options["auto_reload"] = auto_reload
    
    rv = self.jinja_environment(self, **options)
    rv.globals.update(
        url_for=self.url_for,
        get_flashed_messages=get_flashed_messages,
        config=self.config,
        request=request,
        session=session,
        g=g,
    )
    return rv

Template Context

Automatic Variables

From templating.py:21-33, Flask automatically injects these variables:
{# These are always available #}
{{ config.DEBUG }}
{{ request.method }}
{{ session.user_id }}
{{ g.user }}
{{ url_for('index') }}
{{ get_flashed_messages() }}

Passing Variables

Pass data to templates:
@app.route('/product/<int:id>')
def product(id):
    product = get_product(id)
    related = get_related_products(id)
    
    return render_template(
        'product.html',
        product=product,
        related=related,
        page_title=f'{product.name} - Store'
    )
In the template:
<h1>{{ product.name }}</h1>
<p>Price: ${{ product.price }}</p>

<h2>Related Products</h2>
{% for item in related %}
    <div>{{ item.name }}</div>
{% endfor %}

Template Syntax

Variables

{{ variable }}
{{ user.name }}
{{ items[0] }}
{{ dict['key'] }}

Control Structures

Conditionals

{% if user.is_authenticated %}
    <p>Welcome, {{ user.name }}!</p>
{% elif user.is_guest %}
    <p>Welcome, Guest!</p>
{% else %}
    <p>Please log in.</p>
{% endif %}

Loops

{% for item in items %}
    <li>{{ item.name }} - ${{ item.price }}</li>
{% else %}
    <li>No items found.</li>
{% endfor %}

{# Loop variables #}
{% for user in users %}
    {{ loop.index }}: {{ user.name }}
    {% if loop.first %}(First){% endif %}
    {% if loop.last %}(Last){% endif %}
{% endfor %}

Filters

Transform variables:
{{ name|upper }}
{{ text|truncate(20) }}
{{ price|round(2) }}
{{ date|datetimeformat('%Y-%m-%d') }}
{{ items|length }}
{{ html|safe }}  {# Disable escaping #}

Template Inheritance

Base template (base.html):
<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}My Site{% endblock %}</title>
    {% block head %}{% endblock %}
</head>
<body>
    <header>
        {% block header %}
        <h1>My Site</h1>
        {% endblock %}
    </header>
    
    <main>
        {% block content %}{% endblock %}
    </main>
    
    <footer>
        {% block footer %}
        &copy; 2024
        {% endblock %}
    </footer>
</body>
</html>
Child template:
{% extends "base.html" %}

{% block title %}Home - {{ super() }}{% endblock %}

{% block content %}
    <h2>Welcome!</h2>
    <p>This is the home page.</p>
{% endblock %}

Template Inclusion

Include reusable components:
{# _navigation.html #}
<nav>
    <a href="{{ url_for('index') }}">Home</a>
    <a href="{{ url_for('about') }}">About</a>
</nav>

{# index.html #}
{% include '_navigation.html' %}

{# With variables #}
{% include 'components/card.html' with context %}
{% include 'components/button.html', label='Click Me', color='blue' %}

Macros

Create reusable template functions:
{# macros.html #}
{% macro render_field(field) %}
    <div class="field">
        {{ field.label }}
        {{ field(**kwargs)|safe }}
        {% if field.errors %}
            <ul class="errors">
            {% for error in field.errors %}
                <li>{{ error }}</li>
            {% endfor %}
            </ul>
        {% endif %}
    </div>
{% endmacro %}

{# form.html #}
{% from 'macros.html' import render_field %}

<form method="post">
    {{ render_field(form.username) }}
    {{ render_field(form.password) }}
</form>

Custom Template Context

Context Processors

From app.py:590-618, add variables to all templates:
@app.context_processor
def inject_user():
    return dict(current_user=get_current_user())

@app.context_processor
def utility_processor():
    def format_price(amount):
        return f'${amount:.2f}'
    return dict(format_price=format_price)
Use in templates:
<p>Logged in as: {{ current_user.name }}</p>
<p>Price: {{ format_price(19.99) }}</p>

Custom Filters

Register custom Jinja2 filters:
@app.template_filter('reverse')
def reverse_filter(s):
    return s[::-1]

# Or use add_template_filter
def datetimeformat(value, format='%H:%M %d-%m-%Y'):
    return value.strftime(format)

app.jinja_env.filters['datetimeformat'] = datetimeformat
Use in templates:
{{ "hello"|reverse }}  {# olleh #}
{{ article.pub_date|datetimeformat }}
{{ article.pub_date|datetimeformat('%Y-%m-%d') }}

Custom Tests

Create custom template tests:
@app.template_test('prime')
def is_prime(n):
    if n < 2:
        return False
    for i in range(2, int(n**0.5) + 1):
        if n % i == 0:
            return False
    return True
Use in templates:
{% if value is prime %}
    {{ value }} is a prime number!
{% endif %}

Global Functions

Add custom global functions:
@app.template_global()
def double(n):
    return n * 2

# Or use add_template_global
def generate_token():
    return secrets.token_hex(16)

app.jinja_env.globals['generate_token'] = generate_token

Rendering from Strings

From templating.py:151-160, render template strings:
from flask import render_template_string

@app.route('/dynamic')
def dynamic():
    template = '''
        <h1>Hello, {{ name }}!</h1>
        <p>You have {{ messages|length }} messages.</p>
    '''
    return render_template_string(
        template,
        name='John',
        messages=['msg1', 'msg2']
    )

Streaming Templates

From templating.py:181-212, stream large templates:
from flask import stream_template

@app.route('/large-page')
def large_page():
    # Returns an iterator
    return stream_template(
        'large_page.html',
        items=generate_many_items()
    )

Auto-escaping

Flask automatically escapes HTML:
@app.route('/safe')
def safe():
    # This is safe
    user_input = '<script>alert("xss")</script>'
    return render_template('page.html', content=user_input)
{# Automatically escaped #}
{{ content }}  {# &lt;script&gt;... #}

{# Disable escaping (dangerous!) #}
{{ content|safe }}  {# <script>... #}

{# Autoescape control #}
{% autoescape false %}
    {{ content }}
{% endautoescape %}

Blueprint Templates

Blueprints can have their own template folders:
admin = Blueprint('admin', __name__,
                 template_folder='templates',
                 static_folder='static')

@admin.route('/')
def index():
    # Looks in blueprint's template folder first
    return render_template('admin/index.html')
From templating.py:49-121, Flask’s DispatchingJinjaLoader searches:
  1. Blueprint template folders
  2. Application template folder

Template Loading

Custom Loader

Customize template loading:
from jinja2 import FileSystemLoader, ChoiceLoader

app.jinja_loader = ChoiceLoader([
    FileSystemLoader('templates'),
    FileSystemLoader('custom_templates'),
])

Debug Template Loading

Enable template loading explanation:
app.config['EXPLAIN_TEMPLATE_LOADING'] = True

Best Practices

Template Inheritance

Use base templates to avoid repetition

Security First

Never disable auto-escaping unless absolutely necessary

Separation of Concerns

Keep logic in Python, presentation in templates

Reusable Components

Use macros and includes for common components

Build docs developers (and LLMs) love