Jinja2 Templates
Introduction
Jinja2 renders HTML from templates with variables, loops, and inheritance—Flask’s default engine. This chapter sets up templates/ and static/, uses render_template, builds a base layout, includes partials, links CSS with url_for, and introduces filters and macros.
Prerequisites
- Responses and JSON API
- Basic HTML—see HTML track
Project Structure
flask-hello/
├── app.py
├── templates/
│ ├── base.html
│ ├── index.html
│ └── partials/
│ └── nav.html
└── static/
├── css/
│ └── style.css
└── js/
└── app.jsrender_template
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def index():
return render_template("index.html", title="Home", user="Alice")templates/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
</head>
<body>
<h1>Welcome, {{ user }}</h1>
</body>
</html>Code explanation:
- Keyword args to
render_templatebecome template variables - Flask looks in
templates/next to the app root or package
Variables, Statements, Comments
Pass data from view:
return render_template(
"list.html",
items=["Flask", "Jinja2", "Werkzeug"],
logged_in=True,
)Template Inheritance
templates/base.html:
templates/index.html:
{% extends "base.html" %}
{% block title %}Home{% endblock %}
{% block content %}
<h1>Hello, {{ user }}</h1>
{% endblock %}Code explanation:
extendsmust be first meaningful statement in child templateblocknames must match between parent and child
Static Files
static/css/style.css:
body {
font-family: system-ui, sans-serif;
margin: 2rem;
}Link in template:
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<script src="{{ url_for('static', filename='js/app.js') }}" defer></script>Code explanation:
url_for('static', filename=...)generates/static/...URLs- Production may serve static via Nginx instead of Flask
Include Partials
templates/partials/nav.html:
<nav>
<a href="{{ url_for('index') }}">Home</a>
<a href="{{ url_for('about') }}">About</a>
</nav>Register routes so url_for('about') resolves:
@app.route("/about")
def about():
return render_template("about.html", title="About")Filters
Built-in:
{{ name|title }}
{{ text|truncate(80) }}
{{ created_at|default("unknown") }}Custom filter:
@app.template_filter("reverse")
def reverse_filter(s):
return s[::-1]Template:
{{ "Flask"|reverse }}Macros
templates/macros/forms.html:
{% macro input_field(name, label, type="text") %}
<label for="{{ name }}">{{ label }}</label>
<input id="{{ name }}" name="{{ name }}" type="{{ type }}">
{% endmacro %}Use:
{% from 'macros/forms.html' import input_field %}
{{ input_field("email", "Email", "email") }}Forms chapter uses Flask-WTF for CSRF-safe forms.
Tip
Auto-Escape Is On
Jinja2 escapes HTML in {{ }} by default—safe against XSS when rendering user input.
FAQ
TemplateNotFound?
Check folder name templates/, file path, and Flask app root.
CSS not loading?
Verify static/ path and url_for('static', ...); hard refresh browser.
extends order error?
Child template: {% extends %} first, then {% block %}.
Raw HTML from user?
Use {{ content|safe }} only for trusted content—otherwise keep auto-escape.
Multiple template folders?
Flask(__name__, template_folder='views/templates').
Jinja vs JavaScript {{?
Server renders Jinja before browser sees page—client frameworks use different syntax.