Flask template rendering

Concatenating HTML strings in Python code is both tedious and dangerous (prone to XSS vulnerabilities). Flask integrates the Jinja2 template engine to solve this problem.

Templates allow you to separate business logic from page presentation, writing safer and more maintainable code.


render_template Basics

render_template()It is the most commonly used template rendering function in Flask.

Its first parameter is the template file name, and the following keyword arguments are passed to the template as variables.

Example

# File path: app.py
from flask import Flask, render_template

app = Flask(__name__)

@app.route("/")
def index():
    # Render the templates/index.html template, passing the title and name variables
    return render_template("index.html", title=EXAMPLE Home, name="World")

@app.route("/user/<username>")
def profile(username):
    # Pass the username from the URL to the template
    return render_template("user.html", username=username, posts=[
        {"title": Flask Introduction, "date": "2026-05-01"},
        {"title": Jinja2 Template, "date": "2026-05-10"},
    ])

Template files are placed in the project directorytemplates/In the folder:

example-flask-test/
├── app.py
└── templates/
    ├── index.html
    └── user.html

The content of index.html is:

Example (index.html)

<!-- File path: templates/index.html -->
<!DOCTYPE html>
<html>
<head>
    <title>{{ title }} - EXAMPLE</title>
</head>
<body>
    <h1>{{ title }} </h1>
     <p>Hello {{ name }}。</p>
</body>
</html>

Visithttp://127.0.0.1:5000/, the output result is:


Jinja2 basic syntax

Three core syntaxes in Jinja2 templates:

Syntax Purpose Example
{{ ... }} Output variable values, automatically escape HTML {{ username }}
{% ... %} Control statements (if, for, block, etc.) {% if user %}
{# ... #} Comments, will not appear in the rendered result {# This is a comment #}

Template Example

Example (user.html)

<!-- File path: templates/user.html -->
<!DOCTYPE html>
<html>
<head>
    <title>{{ username }} - EXAMPLE</title>
</head>
<body>
    <h1>{{ username }}'s Personal Homepage</h1>

    <!-- Conditional: if the posts list is empty, display a prompt message -->
    {% if posts %}
        <h2>Recent Articles:</h2>
        <ul>
        <!-- Loop over the posts list -->
        {% for post in posts %}
            <!-- loop.index is a built-in Jinja2 variable, starting from 1 -->
            <li>{{ loop.index }}. {{ post.title }} ({{ post.date }})</li>
        {% endfor %}
        </ul>
    {% else %}
        <p>No articles yet</p>
    {% endif %}

    <!-- Using the filter: upper converts text to uppercase -->
    <p>Uppercase username: {{ username|upper }}</p>
</body>
</html>

Common Filters

Filters are used to modify the output format of variables, using the pipe symbol.|Call.

Filter Function Example Output
upper Convert to uppercase {{ "hello"|upper }} HELLO
lower Convert to lowercase {{ "HELLO"|lower }} hello
title Capitalize first letter {{ "hello world"|title }} Hello World
length Get length {{ [1,2,3]|length }} 3
default Set default value {{ name|default("anonymous") }} Anonymous (when name is empty)
safe Mark as safe HTML (no escaping) {{ "Bold"|safe }} Rendered as bold text
join Join list {{ ["a","b"]|join(",") }} a,b

Security Warning:safeThe safe filter disables auto-escaping; use it only when you absolutely trust the data source. Never use it for user input.safe。


Auto-escaping — XSS protection

Jinja2 escapes HTML on all variable output by default; this is a key defense for web security.

For example, if the username submitted by the user is<script>alert("xss")</script>, when the template renders, it is automatically converted to safe text:

<!-- 模板中直接使用变量 -->
<p>{{ username }}</p>

<!-- 渲染结果(HTML 特殊字符已被转义) -->
&lt;p&gt;&amp;lt;script&amp;gt;alert(&amp;quot;xss&amp;quot;)&amp;lt;/script&amp;gt;&lt;/p&gt;

Escaping applies to.html、.htm、.xml、.xhtml、.svgThe template file at the end.


Template inheritance — eliminating duplicate code

Template inheritance is one of Jinja2's most powerful features. It allows you to define a "base layout" and then fill in content via child templates.

This avoids repeatedly writing common parts such as headers, navigation, footers, etc. on every page.

Base template (parent template)

Example

<!-- File path: templates/base.html -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>{% block title %}EXAMPLE Flask Tutorial{% endblock %}</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
    <header>
        <h1>EXAMPLE Flask Tutorial</h1>
        <nav>
            <a href="/">Home</a> |
            <a href="/about">About</a>
        </nav>
    </header>

    <main>
    <!-- block is a placeholder area that child templates can fill or override -->
    {% block content %}{% endblock %}
    </main>

    <footer>
        <p>&copy; 2026 example.com</p>
    </footer>
</body>
</html>

Child template (inherits parent template)

Example

<!-- File path: templates/index.html -->
{% extends "base.html" %}

<!-- Overrides the parent template's title block -->
{% block title %}EXAMPLE Tutorial - Home{% endblock %}

<!-- Fills the parent template's content block -->
{% block content %}
    <h2>Welcome to EXAMPLE</h2>
    <p>{{ greeting }}</p>

    {% if user %}
        <p>Current user: {{ user }}</p>
    {% else %}
        <p><a href="/login">Please log in first</a></p>
    {% endif %}
{% endblock %}
Tags Function
{% extends "base.html" %} Declare that the current template inherits from base.html (must be placed on the first line)
{% block name %}...{% endblock %} Define a block that can be overridden by child templates
{{ super() }} Call the content of the parent template's block with the same name inside the child template's block.

After using template inheritance, you only need to modifybase.htmlOne place, and the common parts of all pages (such as navigation, footer) will automatically update.


Built-in objects in templates

The following Flask objects can be used directly in templates without being passed viarender_template()Pass:

Object Description Usage example in template
request Current request object {{ request.path }}
session Current session data {{ session.get("username") }}
g Request-level global variables {{ g.user }}
config Application Configuration {{ config["APP_NAME"] }}
url_for() URL generation function {{ url_for("index") }}
get_flashed_messages() Get flash messages {% for msg in get_flashed_messages() %}...

Including other templates — include

Usage{% include %}You can embed another template within one template:

Example

<!-- File path: templates/_navbar.html (underscore prefix indicates a partial template) -->
<nav style="background:#f0f0f0;padding:10px;">
    <a href="/">Home</a> |
    <a href="/posts">Articles</a> |
    <a href="/about">About</a>
</nav>

Example

<!-- Include the navigation bar in any template -->
<!DOCTYPE html>
<html>
<head><title>Page</title></head>
<body>
    {% include "_navbar.html" %}

    <h1>Page content</h1>
    <p>This is the main content of the page.</p>
</body>
</html>

Best Practices: Template inheritance (extends) is used for page-level layout reuse,includeUsed for component-level code reuse. Reusable components such as navigation bars and sidebars are suitable for include.


Complete example: Blog homepage

Integrate the above knowledge into an actual scenario:

Example

# File path: app.py
from flask import Flask, render_template

app = Flask(__name__)

@app.route("/")
def index():
    # Simulate article data in the database.
    articles = [
        {"id": 1, "title": Flask Getting Started Guide, "author": "example", "views": 1024},
        {"id": 2, "title": Jinja2 Templates Explained, "author": "admin", "views": 512},
        {"id": 3, "title": RESTful API Design, "author": "example", "views": 256},
    ]
    return render_template("blog.html", articles=articles)

Example

<!-- File path: templates/blog.html -->
{% extends "base.html" %}

{% block title %}EXAMPLE Blog{% endblock %}

{% block content %}
    <h2>Latest Articles</h2>

    {% if articles %}
        {% for article in articles %}
            <!-- Add different styles according to view count -->
            <div class="article" {% if article.views > 1000 %}style="background:#fff3cd;"{% endif %}>
                <h3>{{ article.title }}</h3>
                <p>Author: {{ article.author }} | Views: {{ article.views }}</p>
                <!-- Use the loop variable to create a separator -->
                {% if not loop.last %}
                    <hr>
                {% endif %}
            </div>
        {% endfor %}
    {% else %}
        <p>No articles yet, please come back later.</p>
    {% endif %}
{% endblock %}

Jinja2 special variables

Variable Description
loop.index Current loop index, starts at 1
loop.index0 Current loop index, starts at 0
loop.first Whether it is the first iteration of the loop
loop.last Whether it is the last iteration of the loop
loop.length Total length of the sequence
other extensions