Templates

Flask uses Jinja2 as its template engine for generating HTML. Templates separate page structure from Python logic, and variables are HTML-escaped by default, providing built-in XSS protection.

Basic Usage

from flask import render_template

@app.get("/")
def index():
    return render_template("index.html", name="Alice", items=["a", "b"])

render_template looks up templates in the application's (or blueprint's) templates/ directory; keyword arguments become template variables.

Template Syntax

<!-- Variable output (HTML-escaped automatically) -->
<p>{{ name }}</p>

<!-- Attribute / subscript access -->
<p>{{ user.email }} / {{ row["title"] }}</p>

<!-- Conditionals -->
{% if user %}
  <p>Welcome, {{ user.name }}</p>
{% else %}
  <a href="{{ url_for('auth.login') }}">Log in</a>
{% endif %}

<!-- Loops -->
<ul>
  {% for item in items %}
    <li>{{ loop.index }}. {{ item }}</li>
  {% else %}
    <li>No items yet</li>
  {% endfor %}
</ul>

<!-- Filters -->
{{ name|upper }}  {{ content|truncate(100) }}  {{ price|round(2) }}

The loop object exposes loop state such as index (1-based), first, and last.

Template Inheritance

Define the skeleton in a base template; child templates fill in only what differs:

<!-- templates/base.html -->
<!doctype html>
<html>
<head>
  <title>{% block title %}Site{% endblock %}</title>
  <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
  <nav>…</nav>
  <main>{% block content %}{% endblock %}</main>
</body>
</html>
<!-- templates/index.html -->
{% extends "base.html" %}
{% block title %}Home{% endblock %}
{% block content %}
  <h1>Hello, {{ name }}!</h1>
{% endblock %}

Reusable Fragments: include and Macros

{% include "partials/flash.html" %}

{% macro field(label, name, type="text") %}
  <label>{{ label }} <input type="{{ type }}" name="{{ name }}"></label>
{% endmacro %}

{{ field("Email", "email", type="email") }}

Objects Available in Every Template

Flask injects these automatically: request, session, g, url_for(), get_flashed_messages(), config.

<a href="{{ url_for('index') }}" {% if request.path == '/' %}class="active"{% endif %}>Home</a>

Custom Filters and Globals

@app.template_filter("datefmt")
def datefmt(value, fmt="%Y-%m-%d"):
    return value.strftime(fmt)

@app.context_processor
def inject_globals():
    return {"site_name": "Kenhuang Academy"}    # {{ site_name }} available in all templates

Escaping and Safety

  • Variables are escaped by default; output trusted HTML with {{ content|safe }} — but never apply safe to user input.
  • On the Python side, markupsafe.Markup marks a string as safe.

Static files (CSS/JS/images) are covered in the next chapter.