Introduction

Jinja

Jinja, renowned as a versatile and powerful templating engine for Python, stands as a cornerstone for generating dynamic content across various domains. Initially inspired by Django's templating system, Jinja has evolved into a standalone project celebrated for its simplicity, flexibility, and robust feature set.

At its core, Jinja excels in separating presentation logic from application logic, a fundamental principle in modern software development. Its templating syntax, reminiscent of Python, enables developers to craft elegant and maintainable templates with ease. Beyond web development, Jinja finds utility in a myriad of applications, ranging from generating configuration files to producing email templates and rendering reports.

Jinja with Flask

In the realm of web development, Jinja's integration with Flask, a lightweight and modular web framework, further amplifies its capabilities. Flask seamlessly incorporates Jinja, empowering developers to craft dynamic web applications effortlessly. By leveraging Jinja's templating prowess within Flask, developers can efficiently generate HTML content, manage dynamic data, and deliver rich user experiences.

Let's see an example.

  • First let's create a minimal Flask application with just a single endpoint at main.py .
from flask import Flask, render_template

app = Flask(__name__, template_folder='.')

@app.route("/", methods=["GET"])
def home():
    return render_template(
        'index.html',
        my_list=list(range(10))
    )


if __name__ == '__main__':
    app.run(host="127.0.0.1", port=8080, debug=True)
  • During Flask app creation we are defining the folder where Flask is supposed to find our template, in our case the same folder as the main.py .
  • In the handler return statement we render the template named as index.html which is present in the same folder and contains the following:
<!DOCTYPE html>
<html lang="en">

<head>
    <title>Home page</title>
</head>

<body>
    <div class="row">
        <div class="outter">
        <p class="col-lg-2">My list is: {{my_list}}</p>
    </div>
</body>

</html>
  • Variable my_list is passed in the template context and rendered by the Jinja template engine when referenced with the appropriate syntax {{my_list}} .
  • Visiting the app in the browser looks like this:
None
Screenshot by author

Jinja filters

Jinja filters serve as miniature functions embedded within templates, enabling developers to manipulate data on the fly. These filters facilitate a myriad of operations, including string manipulation, date formatting, and mathematical computations, among others.

To apply a filter in Jinja, you use the pipe symbol (|) followed by the name of the filter. For instance, if you have a variable message containing a string, and you want to convert it to uppercase, you can use an upper filter like this:

{{ message|upper }}

This will output the string stored in message variable in uppercase. Similarly, you can apply other filters by chaining them after the pipe symbol to manipulate data in various ways directly within your Jinja templates.

Built-in Jinja filters

Jinja's built-in filters cover a wide range of common tasks, including string manipulation, date formatting, and mathematical computations. The whole list of supported, built-in filters can be found in the documentation. These filters offer developers a streamlined approach to perform operations such as uppercasing, lowercase, string formatting, and more, without the need for additional Python code. Whether it's formatting dates, truncating strings, or joining lists, Jinja's built-in filters provide a robust toolkit for handling data transformations directly within templates. This feature simplifies template logic and enhances readability, enabling developers to efficiently manage dynamic content without cluttering their codebase.

Let's see an example where we use the same Flask app as before, but now we will just present the maximum value of the list.

  • main.py remains intact, no changes here.
from flask import Flask, render_template


app = Flask(__name__, template_folder='.')


@app.route("/", methods=["GET"])
def home():
    return render_template(
        'index.html',
        my_list=list(range(10))
    )


if __name__ == '__main__':
    app.run(host="127.0.0.1", port=8080, debug=True)
  • index.html is modified to include the built-in max filter, applied on my_list variable. This filter receives the list and returns the maximum value of it.
<!DOCTYPE html>
<html lang="en">

<head>
    <title>Home page</title>
</head>

<body>
    <div class="row">
        <div class="outter">
        <p class="col-lg-2">Max value of list is: {{my_list|max}}</p>
    </div>
</body>

</html>
  • This is how it looks now in our browser:
None
Screenshot by author

Custom Jinja filters

In addition to its rich array of built-in filters, Jinja empowers developers to create custom filters tailored to specific application requirements. Custom filters extend Jinja's functionality by enabling developers to define their own data transformations within templates. Whether it's implementing domain-specific formatting rules, performing complex data manipulations, or integrating with external services, Jinja's custom filters offer a flexible and modular solution. By encapsulating custom logic within filters, developers can enhance code reusability, maintainability, and scalability in their Flask applications. This versatility not only expands Jinja's capabilities but also empowers developers to adapt templates dynamically to evolving project needs, fostering a more agile and efficient development process.

Use case

  • We are going to change our Flask app endpoint to just to pass the current date in our template and present it to the user:
from flask import Flask, render_template
from datetime import datetime, timezone


app = Flask(__name__, template_folder='.')


@app.route("/", methods=["GET"])
def home():
    return render_template(
        'index.html',
        current_date=datetime.now(tz=timezone.utc)
    )


if __name__ == '__main__':
    app.run(host="127.0.0.1", port=8080, debug=True)
  • At first we don't apply a filter in our template, the index.html looks like this:
<!DOCTYPE html>
<html lang="en">

<head>
    <title>Home page</title>
</head>

<body>
    <div class="row">
        <div class="outter">
        <p class="col-lg-2">Current date is: {{current_date}}</p>
    </div>
</body>

</html>
  • The template rendering looks like this in the browser:
None
Screenshot by author
None

Weeell, the above date format is not very user friendly, is it?

Let's change that. We will use a custom filter to manipulate that variable and expose it in a more readable way.

Bear with me.

Of course, this use case is trivial and the string manipulation could just happen in the main.py in the first place, but imagine utility functions pile up in an expanding project. Noone wants to deal with all the representation utility functions in their core logic code.

Anyways, let's set a desired look for our date:

None
Screenshot by author

Much clearer and human readable.

Let's see how we can achieve this.

Filter function

First we need to create a function that does the needed transformation.

This would look like this:

def human_datetime(datetime_obj: datetime):
    return datetime_obj.strftime("%H:%M %B %d, %Y")

This function expects a datetime object as input and returns the string representation of it, in the format we set earlier as expected.

Now that we have the transforming function, let's see how we can register it and use it as a custom filter for our variable in index.html template, we got a couple of ways.

Decorator syntax

Most common way to declare a custom template filter for Jinja in Flask is to use the decorator syntax. This would look like this in our main.py :

app = Flask(__name__, template_folder='.')

@app.template_filter()
def human_datetime(datetime_obj: datetime):
    return datetime_obj.strftime("%H:%M %B %d, %Y")
  • Since we didn't provide an argument in the wrapper function template_filterthe filter function's name is used as the filter name registered in Jinja engine. That means that we can use it now in our template with the | pipe syntax like this:
<p class="col-lg-2">Current date is: {{current_date|human_datetime}}</p>

One can define an arbitrary filter name during filter registration, by just passing the name in the template_filter method, like this:

app = Flask(__name__, template_folder='.')

@app.template_filter("my_custom_filter")
def human_datetime(datetime_obj: datetime):
    return datetime_obj.strftime("%H:%M %B %d, %Y")

In that case, the filter will be available in our templates with that arbitrary name like this:

<p class="col-lg-2">Current date is: {{current_date|my_custom_filter}}</p>

Alternative decorator syntax

A slightly different approach in registering custom Jinja filters in Flask apps, is to use the very same wrapper method template_filter but in different syntax.

Like this:

def human_datetime(datetime_obj: datetime):
    return datetime_obj.strftime("%H:%M %B %d, %Y")

app = Flask(__name__, template_folder='.')
app.template_filter()(human_datetime)

Again, one can declare an arbitrary filter name with that syntax and this is the way:

def human_datetime(datetime_obj: datetime):
    return datetime_obj.strftime("%H:%M %B %d, %Y")


app = Flask(__name__, template_folder='.')
app.template_filter("my_custom_filter")(human_datetime)

This slightly different syntax offers a few benefits, most importantly:

  1. Separation of Concerns: This syntax keeps the filter registration separate from the filter implementation. It promotes modular code organization, where filter functions can be defined independently of their registration within the Flask application. This can lead to cleaner and more readable code, especially when dealing with multiple filters.
  2. Flexibility: It allows for dynamic filter registration. Since the app.template_filter() method can be invoked with different filter names and functions at runtime, it provides flexibility in defining and managing Jinja filters within the application.

Dictionary syntax

Another approach for registering custom Jinja template filters is via the Jinja environment instance of Flask app.

The environment instance of Jinja that is used by our Flask app is accessible via the attribute jinja_env on the Flask app instance and the filters of it are accessible via the attribute filters on the Jinja environment instance.

Using that way to register our custom filter would look like this:

app = Flask(__name__, template_folder='.')


def human_datetime(datetime_obj: datetime):
    return datetime_obj.strftime("%H:%M %B %d, %Y")


app.jinja_env.filters[human_datetime.__name__] = human_datetime

Defining arbitrary filter name is of course possible as well:

app = Flask(__name__, template_folder='.')


def human_datetime(datetime_obj: datetime):
    return datetime_obj.strftime("%H:%M %B %d, %Y")


app.jinja_env.filters["my_custom_filter"] = human_datetime

Conclusion

In conclusion, Jinja custom filters in Flask offer a powerful toolset for enhancing the dynamism and efficiency of your web applications. By mastering the art of custom filters, developers can seamlessly manipulate data within templates, elevate user experiences, and streamline development workflows while maintaining a clean core logic codebase. Embrace the possibilities of Jinja custom filters in your Flask projects, and embark on a journey to unlock new dimensions of creativity and efficiency in web development!

That's all folks!

Thank you for taking the time to read through to the end — I truly hope you found this article valuable!

How to support me

If you found this article useful consider showing your appreciation by:

  • 👏 Leaving a round of applause
  • ✍️ Sharing your feedback in the comments section below
  • 🤌 Highlighting your favourite parts
  • 📣 Sharing it with whoever might be interested

Like what you read and want more?

  • 👉 Hit the follow button and stay updated on future posts!
  • 🔔 Subscribe to get an email whenever a new story of mine is out!
  • 🔗 Follow me: GitHub | LinkedIn | X

Enjoyed this piece? Consider buying me a coffee to support future content creation and help keep the coffee flowing!

Until next time, happy reading and coding!