Templates with Jinja2
Passing data into templates, inheritance with blocks, escaping (and the one line that disables it).
Rendering
from flask import render_template
@app.get("/posts")
def posts():
items = [{"title": "Hello", "slug": "hello"}]
return render_template("posts.html", posts=items, user=None){# templates/posts.html #}
<h1>Posts</h1>
<ul>
{% for p in posts %}
<li><a href="{{ url_for('show_post', post_id=p.id) }}">{{ p.title }}</a></li>
{% else %}
<li>No posts yet.</li>
{% endfor %}
</ul>
{% if user %}
<p>Signed in as {{ user.name }}</p>
{% endif %}Output is auto-escaped: {{ value }} renders HTML special characters safely, which is what protects you from XSS.
Template inheritance
{# templates/base.html #}
<!DOCTYPE html>
<html lang="en">
<head><title>{% block title %}Site{% endblock %}</title></head>
<body>
<header>{% include "_nav.html" %}</header>
<main>{% block content %}{% endblock %}</main>
</body>
</html>{# templates/posts.html #}
{% extends "base.html" %}
{% block title %}Posts · Site{% endblock %}
{% block content %}
<h1>Posts</h1>
{% endblock %}⚠️
{{ value | safe }} disables escaping. Only use it for HTML you generated yourself — never for user input. The same applies to | safe applied to a whole block of user content.Filters and macros
{{ price | round(2) }}
{{ created_at | datetimeformat }}
{{ text | truncate(80) }}
{{ items | length }}
{{ name | default("anonymous") }}
{% macro field(name, label) %}
<label for="{{ name }}">{{ label }}</label>
<input id="{{ name }}" name="{{ name }}">
{% endmacro %}Register your own filters with @app.template_filter — it keeps display formatting out of view functions.
FAQ
Where does Flask look for templates?
A
templates/ folder beside the app module (or the package root for an application factory). Pass template_folder= to change it.How do I avoid repeating navigation markup?
Put it in the base template or an
{% include %} partial. Use {% block %} for the parts that differ.Related
Your first Flask app Forms, sessions and errors
Last refreshed 2026-09-18.