Skip to main content
Templates are files that contain static data as well as placeholders for dynamic data. A template is rendered with specific data to produce a final document. Flask uses the Jinja template library to render templates.

Jinja Template Basics

Jinja looks and behaves mostly like Python. Special delimiters distinguish Jinja syntax from static data:
  • {{ }} - Expression that will be output to the final document
  • {% %} - Control flow statement like if and for
  • {# #} - Comments (not included in output)
Flask configures Jinja to autoescape any data rendered in HTML templates. This means it’s safe to render user input - characters like < and > will be escaped with safe values.

The Base Layout

1

Create base template

Each page in the application will have the same basic layout. Instead of writing the entire HTML structure in each template, each template will extend a base template.Create flaskr/templates/base.html:
flaskr/templates/base.html
<!doctype html>
<title>{% block title %}{% endblock %} - Flaskr</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<nav>
  <h1><a href="{{ url_for('index') }}">Flaskr</a></h1>
  <ul>
    {% if g.user %}
      <li><span>{{ g.user['username'] }}</span>
      <li><a href="{{ url_for('auth.logout') }}">Log Out</a>
    {% else %}
      <li><a href="{{ url_for('auth.register') }}">Register</a>
      <li><a href="{{ url_for('auth.login') }}">Log In</a>
    {% endif %}
  </ul>
</nav>
<section class="content">
  <header>
    {% block header %}{% endblock %}
  </header>
  {% for message in get_flashed_messages() %}
    <div class="flash">{{ message }}</div>
  {% endfor %}
  {% block content %}{% endblock %}
</section>
Key features:
  • g is automatically available in templates
  • url_for() is automatically available and generates URLs to views
  • The template loops over messages from get_flashed_messages()
  • Three blocks are defined for child templates to override: title, header, and content
2

Create register template

Create flaskr/templates/auth/register.html:
flaskr/templates/auth/register.html
{% extends 'base.html' %}

{% block header %}
  <h1>{% block title %}Register{% endblock %}</h1>
{% endblock %}

{% block content %}
  <form method="post">
    <label for="username">Username</label>
    <input name="username" id="username" required>
    <label for="password">Password</label>
    <input type="password" name="password" id="password" required>
    <input type="submit" value="Register">
  </form>
{% endblock %}
Key features:
  • {% extends 'base.html' %} tells Jinja that this template should replace the blocks from the base template
  • All rendered content must appear inside {% block %} tags that override blocks from the base template
  • The required attribute tells the browser not to submit the form until those fields are filled in
Always fully validate data on the server, even if the client does some validation. Client-side validation can be bypassed.
3

Create login template

Create flaskr/templates/auth/login.html:
flaskr/templates/auth/login.html
{% extends 'base.html' %}

{% block header %}
  <h1>{% block title %}Log In{% endblock %}</h1>
{% endblock %}

{% block content %}
  <form method="post">
    <label for="username">Username</label>
    <input name="username" id="username" required>
    <label for="password">Password</label>
    <input type="password" name="password" id="password" required>
    <input type="submit" value="Log In">
  </form>
{% endblock %}
This is identical to the register template except for the title and submit button.

Test the Templates

1

Start the server

Make sure the server is running:
flask --app flaskr run --debug
2

Visit registration page

3

Test form validation

Try clicking the “Register” button without filling out the form - the browser will show an error message.Try removing the required attributes from the template and click “Register” again. Instead of the browser showing an error, the page will reload and the error from flash() in the view will be shown.
4

Register a user

Fill out a username and password and you’ll be redirected to the login page.
5

Test login

Try entering an incorrect username, or the correct username and incorrect password - you should see the appropriate error messages.
If you log in successfully, you’ll get an error because there’s no index view yet. We’ll create that in the next section.

Template Organization

The base template is directly in the templates directory. To keep the others organized, templates for a blueprint are placed in a directory with the same name as the blueprint:
templates/
├── base.html
├── auth/
│   ├── login.html
│   └── register.html
└── blog/
    ├── create.html
    ├── index.html
    └── update.html

Useful Template Patterns

Nested Blocks

A useful pattern is to place {% block title %} inside {% block header %}. This sets the title block and then outputs the value of it into the header block:
{% block header %}
  <h1>{% block title %}Register{% endblock %}</h1>
{% endblock %}
This way both the window title and page heading share the same title without writing it twice.

Conditional Display

Check if a user is logged in to display different navigation:
{% if g.user %}
  <li><span>{{ g.user['username'] }}</span>
  <li><a href="{{ url_for('auth.logout') }}">Log Out</a>
{% else %}
  <li><a href="{{ url_for('auth.register') }}">Register</a>
  <li><a href="{{ url_for('auth.login') }}">Log In</a>
{% endif %}

Form Data Persistence

Display previously submitted data when there’s a validation error:
<input name="title" value="{{ request.form['title'] }}" required>

Default Values

Use the or operator to provide a default value:
<input name="title" value="{{ request.form['title'] or post['title'] }}" required>

Build docs developers (and LLMs) love