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
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)
<!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)
<!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 特殊字符已被转义) -->
<p>&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;</p>
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
<!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>© 2026 example.com</p>
</footer>
</body>
</html>
Child template (inherits parent template)
Example
{% 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
<nav style="background:#f0f0f0;padding:10px;">
<a href="/">Home</a> |
<a href="/posts">Articles</a> |
<a href="/about">About</a>
</nav>
Example
<!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
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
{% 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 |