Jinja2 in Flask 3

Video thumbnail

One of the problems we currently have is that we don't have an effective way to present data to the end-user. Since Flask is a web technology, the usual way to present this data is on a web page; this is where what is known as a template engine becomes important; a template engine is a resource used to present this data from the server and generate a web page in the process. They are the next layer of the MVC corresponding to the view and allow separating the logic of data presentation; to do this, a set of features is used to manipulate server data and generate the HTML page that will be consumed by the user, features like printing, filters, conditionals, loops, or imports, allow generating the necessary HTML code to represent a web page. They are widely used in server-side programming as they allow data presentation without having to modify the source code.

In short, with a template engine it's possible to present the data handled by the server, specifically in our case in Flask, and generate the HTML in the process; for example, if we have a list of users, we can print it in an HTML table or similar using certain functionalities (directives).

Throughout this guide, I will show you how to use Jinja2 in Flask 3, from basic configuration to template inheritance, covering conditionals, loops, and filters. It is assumed that you already have your app fully functional and are capable of defining the environment variables in Flask using .env files

What is a template engine and what is it for

A template engine is a tool that connects server logic with the browser's visual layer, generating dynamic HTML. In the context of the MVC pattern, it corresponds to the View layer. Thanks to this approach, you can separate data presentation and maintain more easily maintainable code.

Without a template engine, it was almost impossible to display data elegantly; everything boiled down to concatenating HTML strings. Jinja2 came to solve that.

Advantages of using Jinja2 in Flask 3

  • Simple syntax similar to Python.
  • Supports variables, filters, conditionals, and loops.
  • Allows code reuse with blocks and inheritance.
  • It is integrated by default in Flask.

About Jinja

Jinja is a template engine written in Python designed to be simple and expressive; Jinja is a template engine that can be used in other frameworks like FastAPI, it is also very similar to the one used by Django.

With Jinja as the template engine, we can easily represent API responses.

Jinja's template engine uses braces { } to distinguish its expressions and syntax from HTML:

  1. The syntax {{ }} is called a variable block and we can use it to print in the template.
  2. The syntax {% %} houses control structures like if/else, loops, and macros.
  3. The syntax {#  #} is for comments.

Configure Jinja in Flask

By default, Flask looks for a templates folder named `templates` at the application's root level. If this folder is present, Flask will automatically read its content, making the content of this folder available for use with the `render_template()` method from the controllers; so, we will create the aforementioned folder:

app\my_app\templates

Folder structure and templates file

Flask searches by default for templates inside a folder called `templates` located at the root level of the application:

my_app/
├── __init__.py
└── templates/
    └── index.html

Let's start by creating a basic HTML page, which would be a Jinja template:

my_app\templates\index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <title>Page</title>
</head>

<body>
    <h1>Hello Flask</h1>
    <p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Reiciendis libero possimus nihil ipsam quam deleniti
        voluptas eaque qui. Necessitatibus corporis quas odio tenetur quos, recusandae itaque doloremque assumenda
        consequatur aliquam.</p>
</body>

</html>

From a controller, we return the template instead of a text as we had before:

my_app\__init__.py

from flask import render_template, request 

@app.route('/') 
@app.route('/hello') 
def hello_world(): 
    return render_template('index.html') 

Passing dynamic variables from Flask to Jinja2

Let's make some specific changes so we can understand the potential of Jinja combined with server-side frameworks like Flask, which is passing data dynamically from Flask.

In a real application, you will normally want to send data from Flask to the HTML. In my case, I usually do it to customize messages or dynamic lists:

my_app\__init__.py

from flask import Flask, render_template, request
app = Flask(__name__)

@app.route('/hello')
def hello():
    name = request.args.get('name', 'DesarrolloLibre')
    return render_template('index.html', name=name)

With:

request.args.get('name', 'DesarrolloLibre') 

A GET parameter called "name" is obtained; the parameter is then supplied to the template via:

name=name

Where:

name=name

Refers to the variable named name:

name=name

Indicates the way to access the name variable in the controller from the view:

name=name

From the template or view, we print by referencing the variable from the view with double braces:

my_app\templates\index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <title>Page</title>
</head>

<body>
    <h1>Hello {{name}}</h1>
</body>
</html>

And we will see on the screen:

http://127.0.0.1:5000/

Hello DesarrolloLibre

You can customize the name by passing a GET parameter:

http://127.0.0.1:5000/?name=Andrew

Hello Andrew

In case you want to customize the name of the folder or location of the templates, you can define it when creating the main instance of the application:

app = Flask(__name__, template_folder="<TEMPLATELOCATION>")

The various features that Jinja offers us, like the previous one, will be seen in detail in the next section.

First steps with Jinja2: Basic Syntax

Expressions with {{ }}

Everything you enclose within {{ }} is printed directly in the template.
Example:

<p>{{ message }}</p>

In this section, we will learn about the main features of Jinja, ranging from the use of control blocks to the use of filters and the template in general.

Control Structures {% if %}, {% for %} and comments {# #}

One of the crucial elements in template engines are control blocks; we can perform everything from conditionals to for loops that work the same way as in Python; with this, it's possible to perform conditional logic to render HTML blocks if a condition is met, as well as iterate over complete data collections, for example, a list of tasks in a table or similar.

Conditionals

We can make use of conditionals to evaluate true and false conditions:

<div>
    {% if True %}
        Is TRUE
    {% endif %}
</div>

Conditionals in Jinja are exactly the same as in Python, we can use `else`, `or`, `and`, etc.:

<div>
    {% if True and False %}
        Is True
    {% else %}
        Not is True
    {% endif %}
</div>

For loop

Just as with the for loop in Python, we can use it in Jinja:

<ul>
{% for item in items %}
    <li>{{ item }}</li>
{% endfor %}
</ul>

Inside a `for` loop block, you can access some special variables, the main ones being:

  • `loop.index`: Gets the current iteration index starting from one.
  • `loop.index0`: Gets the current iteration index starting from zero.
  • `loop.first`: True if it's the first iteration.
  • `loop.last`: True if it's the last iteration.
  • `loop.length`: Returns the size of the list.

For example, to use the last variable:

<ul>
    {% for e in [1,2,3,4,5,6,10] %}
    <li>{{ e }} {{ loop.last }} </li>
    {% endfor %}
</ul>

All variables must be used inside the `for` loop.

Filters in Jinja2

Filters in Jinja are one of the most important and versatile features in this template engine; they allow manipulating the content displayed in a template. There are several predefined filters in Jinja, although we can extend existing ones with custom filters; filters are applied to variables separated by, for example, counting the length of a string, joining texts into one, separating by some delimiter, formatting dates, numbers, and a long etc.; in summary, they allow performing custom content manipulations.

To use a filter, the pipe operator (|) is used after specifying the argument or arguments, and after the pipe operator, the filter you want to use:

<DATA> | <FILTER>

Let's look at some filters in Jinja.

Default filter

The default filter is used to replace the output if the variable has a null value (is not defined):

{{ task | default('This is a default Task') }}

For example, if task is null, we will have an output like:

This is a default Task

If task were defined or we evaluate, for example, a number.

{{ 5 | default('This is a default Task') }}

We would see the number on the screen:

5

Escape filter

Replaces the characters &, <, >, ' and " in the supplied string with safe characters to display text containing HTML characters:

{{ "<title>Todo Application</title>" | escape }}

We will have as output:

&lt;title1&gt;Todo Application&lt;/title1&gt;

Conversion filter

These filters include int and float filters used to convert from one data type to another:

{{ 3.142 | int }}

We will have as output:

3

Or for example:

{{ 31 | float }}

We will have as output:

31.0

Max filter

Returns the largest element in the list:

{{ [1, 2, 3, 4, 5]|max }}

And we will have:

5

Min filter

Returns the smallest element in the list:

{{ [1, 2, 3, 4, 5]|min }}

And we will have:

1

Inheritance and blocks in templates

What is a base template (master.html)

One of the best features of Jinja2 is template inheritance, which allows reusing a common HTML structure across multiple views.

For example:

<!-- master.html -->
<!DOCTYPE html>
<html lang="es">
<head>
 <title>{% block title %}{% endblock %}</title>
</head>
<body>
 <header>{% block header %}{% endblock %}</header>
 <main>{% block content %}{% endblock %}</main>
</body>
</html>

Many of the functionalities we can use in Jinja reside in code blocks. These look like this:

{% <BLOCKTYPE> %} 
<BODY>
{% <ENDBLOCKTYPE> %}

Code blocks allow for both better code organization and dividing the block into sections using various block types that perform a specific function.

Template Inheritance

In this section, we will see how to use templates in Jinja; specifically how to use master templates to reuse a base skeleton in multiple views and how to include view fragments (other templates) in templates.

In this section, we will use the terms view, template, and template interchangeably.

Master Template

Often it is necessary to have a master view where certain key content is updated; for example, if we have a management module, the common elements between screens could be a header to define links, as well as a logo, the footer, and a container; the dynamic elements are presented in the middle of the screen.

In Jinja, we can use master templates to implement these types of logic; ultimately, a master view is nothing more than a template, where we have special blocks or sections (block) to indicate where the content that will vary goes, and this master template is included in the final templates, and through the blocks specified in the master template, they are referenced in the final templates with the final content; this Jinja functionality is known as template inheritance.

Let's create a base or skeleton template that will be used to be inherited from other templates:

master.html

<!DOCTYPE html>
<html lang="es">
<head>
    {% block head %}
        <link rel="stylesheet" href="style.css" />
        <title>{% block title %}{% endblock %} - DesarrolloLibre</title>
    {% endblock %}
</head>
<body>
    <div id="content">{% block content %}{% endblock %}</div>
    <div id="footer">
        {% block footer %}
           DL by <a href="#">Contact</a>.
        {% endblock %}
    </div>
</body>
</html>

In the previous code, 3 blocks are defined:

  1. One for the title in the header.
  2. One for the title in the content.
  3. Another for the content, which would be the place where we put the list, form among others.

How to extend and override blocks

<!-- home.html -->
{% extends "master.html" %}
{% block title %}Inicio{% endblock %}
{% block content %}
 <h2>Bienvenido a mi sitio</h2>
{% endblock %}

Reusing views with template inheritance

With this pattern, you can change a single block in `master.html` and automatically update your entire application. It's one of the reasons why I think Jinja2 simplifies the lives of Flask developers.

Conclusion

Mastering Jinja2 in Flask 3 will not only allow you to create clean and dynamic interfaces but will also help you keep your application organized under MVC pattern best practices. In my experience, understanding how to pass variables, apply filters, and use template inheritance was a before and after in my way of developing with Flask.

The next natural step would be to integrate these templates with databases and APIs, generating dynamic views with real data.

Frequently Asked Questions (FAQs)

What is Jinja2 in Flask?
It is the template engine that generates dynamic HTML from variables and logic defined in Flask.

Where does Flask store Jinja2 templates?
In the `templates/` folder of the project.

What is the difference between Jinja2 and Django Templates?
They are similar in syntax, but Jinja2 is independent and more flexible, ideal for Flask.

How to pass data from Flask to Jinja2?
By using `render_template()` and passing variables as arguments.

What are filters in Jinja2?
Functions that transform content before display (e.g., `|default`, `|escape`, `|upper`).

The next step is to show other types of data through the use of Flash messages in Flask.

I agree to receive announcements of interest about this Blog.

We will give an introduction to Jinja2 focused on Flask, we will learn about the template engine, filters, template inheritance, blocks among other aspects.

| 👤 Andrés Cruz

🇪🇸 En español