Jinja (specifically Jinja2, its current major version) was created by Armin Ronacher, the same developer behind the Flask web framework.
It draws inspiration from Django's templating system but offers more flexibility and powerful features. The name "Jinja" comes from a Japanese shrine, reflecting the elegant and simple design philosophy behind the engine.
Today, Jinja is widely used across the Python ecosystem, particularly in:
- Web frameworks like Flask and Django.
- Configuration management tools like Ansible and SaltStack.
- Static site generators like Pelican.
- Documentation generators like Sphinx.
- Email templating systems.
- Report generation tools.
What makes Jinja particularly popular is its balance of simplicity and power. It provides a straightforward syntax for basic operations while offering advanced features for complex requirements.
Additionally, Jinja is designed with security in mind, protecting against common vulnerabilities like cross-site scripting (XSS) attacks through automatic escaping.
This guide will walk you through everything you need to know about Jinja, from basic setup to advanced usage patterns, with practical examples along the way.
Setting up Jinja
Before diving into Jinja's features, let's get everything set up properly. Jinja is a Python package, so you'll need Python installed on your system.
The simplest way to install Jinja is via pip, Python's package manager:
This will install the latest stable version of Jinja2. If you're using a virtual environment (which is recommended), make sure to activate it before running this command.
Basic environment setup
To use Jinja in a Python script, you need to import it and set up an environment. Here's a minimal example:
In this example, we're creating a Jinja environment that loads templates from a
directory called templates relative to our script. We then load a specific
template file, provide some data to it, and render the result.
For this to work, you'll need to create a directory called templates and
within it, a file called hello.html with the following content:
When you run the script, it should output:
Integration with Python applications
While the above example shows standalone usage, Jinja is often integrated into larger applications. Many web frameworks already include Jinja or similar templating engines.
For example, in Flask, Jinja is built-in and can be used with minimal configuration:
Standalone usage
You can also use Jinja without file-based templates by using the Template
class directly:
This approach is useful for simpler applications or when templates come from a database or other non-file sources.
Understanding the core concepts
Now that you have Jinja set up, let's explore its core concepts and syntax. Understanding these fundamentals will give you a solid foundation for using Jinja effectively.
Template basics and syntax
Jinja templates are text files with embedded expressions and statements. These elements are enclosed in specific delimiters:
{{ ... }}for expressions (variables or expressions that output a value).{% ... %}for control structures like conditionals and loops.{# ... #}for comments.
Here's a simple template demonstrating these elements:
Variables and expressions
Variables in Jinja templates are passed when rendering the template. They can be simple values or complex objects. You can access attributes and items using dot notation or square brackets:
Jinja also supports expressions within the {{ }} delimiters, so you can write
something like:
Control structures
Jinja provides several control structures to help you create dynamic content. Let's look at some of the most common ones.
The if statement allows you to conditionally display content:
The for loop iterates over sequences like lists and dictionaries:
The else block in a for loop is executed if the sequence is empty.
Template inheritance and reuse
One of Jinja's most powerful features is template inheritance, which allows you to build a base "skeleton" template that contains common elements and defines blocks that child templates can override.
Let's create a base template:
Now, we can create a child template that extends this base:
In this example, the child template inherits all the structure from the base template but overrides specific blocks to customize content.
Filters and tests
Jinja provides filters and tests to modify variables and test conditions.
Filters are applied using the pipe symbol (|) and can transform the output of
an expression:
Tests check if a variable matches certain criteria and are used with the is
keyword:
Macros and functions
Macros are similar to functions in programming languages and allow you to create reusable template fragments.
You can also import macros from other templates:
Building a Flask application with Jinja
Now that you understand the core concepts of Jinja, let's build a practical Flask application that demonstrates these concepts in action. We'll create a simple app that fetches data from a free API and displays it using Jinja templates.
First, let's set up our Flask project with the necessary dependencies:
Next, we'll create a basic project structure:
Now, let's create our basic Flask application in app.py:
In this setup:
- We've created a Flask application with routes for a home page and post detail page.
- We're using the JSONPlaceholder API to fetch blog posts and related data.
- We've added error handlers for 404 and 500 errors.
- We're using a context processor to inject global variables into all templates.
Now, let's create our base template that will serve as the foundation for all
pages. We'll create a file called base.html in the templates folder:
Next, let's create a simple CSS file in the static directory:
Now, let's create our error page templates:
Finally, let's create our home page template, which will display a list of posts from the API:
In this template:
- We're extending the
basetemplate. - We're overriding the title block to include the app name (which comes from our context processor).
- We're iterating through the
postspassed from our Flask route. - We're using the
capitalizefilter to capitalize the post title. - We're using the
truncatefilter to limit the length of the post body. - We're using the
url_forfunction to generate the URL for the post detail page.
You may now start the application on port 5000 by running:
Visiting http://localhost:5000 in your browser will yield the following
results:
At the moment, if you click any of the posts, you'll see a 404 page due to the template not being set up yet:
In the following section, we'll create the post layout template and learn more about template inheritance.
Creating the post template
Next, let's create our post detail template, which will display the details of a single post along with comments:
In this template, we're extending the base template to maintain a consistent layout across our application. We've implemented more complex control structures by utilizing nested conditionals and loops to display the post content and comments dynamically based on available data.
Throughout the template, we access various properties of the post, user, and
comments objects to display the relevant information in appropriate places.
We've enhanced the presentation by applying filters like capitalize to format
text properly and length to calculate and display metadata about the content.
Additionally, we've included some JavaScript functionality within the extra_js
block that was defined in our base template, demonstrating how to incorporate
client-side functionality specific to this page.
When you visit a /post route now, each post will now render correctly.
Creating reusable components with macros
Now let's create some reusable components using macros. We'll create a file
called macros.html in the templates folder:
The post_card macro serves as a reusable component that displays a post in a
consistent card format throughout our application.
It takes a post object and an optional show_body parameter that determines
whether to display the post's content. For example, you might show the body on
the home page but hide it in search results or related posts sections.
Go ahead and update our home page to use the post card macro:
There will be no change to how the home page is rendered visually, but your templates will be more modular and easier to maintain.
In this manner, you can reuse components across multiple templates without duplicating code.
Using filters and tests
Now let's enhance our application by creating custom filters and tests. We'll
add these to our app.py file:
This code extends Jinja's functionality by adding custom template utilities:
A custom filter called
readtimethat calculates estimated reading time for text by:- Assuming a reading speed of 200 words per minute.
- Counting words in the provided text.
- Calculating and rounding minutes needed to read.
- Returning a formatted string like "3 min read".
- Ensuring at least 1 minute is shown even for very short content.
A custom test called
popularthat determines if content is popular by:- Checking if a post has 3 or more comments.
- Returning
Trueif it meets this threshold,Falseotherwise.
These extensions can be used in templates like {{ post.body|readtime }} and
{% if comments is popular %}Popular!{% endif %}, enhancing template
expressiveness without cluttering presentation logic.
You can see them in action by modifying your post_detail.html file as follows:
Once you reload a post, you'll see the read time and "popular" tag where appropriate:
Final thoughts
Jinja templating is a powerful tool that strikes a balance between simplicity and functionality. It allows you to separate your presentation logic from your business logic, making your code more maintainable and your templates more reusable.
In this guide, we've explored Jinja's core concepts and built a practical Flask application that demonstrates these concepts in action.
Thanks for reading!