Skip to main content

Flask Jinja2 Basics

Introduction

When developing web applications with Flask, you'll quickly encounter the need to generate dynamic HTML content. This is where Jinja2 comes in - Flask's default templating engine that allows you to embed Python-like expressions directly into your HTML.

Jinja2 provides an elegant way to separate your application's logic (Python code) from its presentation (HTML templates). This separation makes your code more maintainable, readable, and follows the MVC (Model-View-Controller) design pattern principles.

In this tutorial, we'll explore the fundamentals of Jinja2 templating within Flask applications and learn how to create dynamic web pages efficiently.

What is Jinja2?

Jinja2 is a modern and designer-friendly templating language for Python inspired by Django's template system. It's fast, widely used, and provides features like:

  • Variable expressions
  • Control structures (if/else statements, loops)
  • Template inheritance
  • Macros and inclusion
  • Automatic HTML escaping for security

Flask includes Jinja2 by default, so there's no need for additional installation when using Flask.

Basic Template Rendering

Let's start with a simple example of rendering a template in Flask:

python
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def index():
return render_template('index.html', title='Home Page', message='Welcome to Flask!')

if __name__ == '__main__':
app.run(debug=True)

In this example, the render_template function takes the template name ('index.html') and keyword arguments that will be passed to the template. Now, let's create the template:

html
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>{{ message }}</h1>
<p>This is a simple Jinja2 template.</p>
</body>
</html>

When you run this application and navigate to the root URL, Flask will render the template, replacing {{ title }} with "Home Page" and {{ message }} with "Welcome to Flask!".

Jinja2 Syntax Basics

Delimiters

Jinja2 uses specific delimiters to distinguish template code from regular HTML:

  • {{ ... }}: For expressions or variables that should be printed
  • {% ... %}: For statements like loops or conditionals
  • {# ... #}: For comments (not rendered in the final HTML)
  • ## ... ##: For line statements (an alternative way to write statements)

Variables and Expressions

Displaying variables is straightforward using the double curly braces:

html
<h1>Hello, {{ name }}!</h1>
<p>Your age is {{ age }} years.</p>
<p>In 5 years, you'll be {{ age + 5 }} years old.</p>

You can also access dictionary values, object attributes, and more:

html
<p>User details:</p>
<ul>
<li>Name: {{ user.name }}</li>
<li>Email: {{ user['email'] }}</li>
<li>Role: {{ user.get_role() }}</li>
</ul>

Filters

Jinja2 provides filters to modify variables before displaying them. They are applied using the pipe symbol (|):

html
<!-- Convert to uppercase -->
<p>{{ name|upper }}</p>

<!-- Format a number -->
<p>Price: ${{ price|float|round(2) }}</p>

<!-- Provide a default value if variable is undefined -->
<p>Status: {{ status|default('Unknown') }}</p>

<!-- Escape HTML -->
<p>{{ user_input|e }}</p>

<!-- Limit string length -->
<p>{{ long_text|truncate(100) }}</p>

Here's a Flask example showing filters in action:

python
@app.route('/filters')
def filters_example():
context = {
'name': 'john doe',
'price': 42.9999,
'tags': ['flask', 'python', 'web'],
'html_content': '<script>alert("dangerous!")</script>',
'long_description': 'This is a very long text that we want to truncate for display purposes.'
}
return render_template('filters.html', **context)

And the corresponding template:

html
<h2>Filter Examples</h2>
<p>Original name: {{ name }}</p>
<p>Capitalized name: {{ name|capitalize }}</p>
<p>Formatted price: ${{ price|round(2) }}</p>
<p>Tags joined: {{ tags|join(', ') }}</p>
<p>Safe HTML (careful!): {{ html_content|safe }}</p>
<p>Truncated text: {{ long_description|truncate(20) }}</p>

Output:

Filter Examples
Original name: john doe
Capitalized name: John doe
Formatted price: $43.0
Tags joined: flask, python, web
Safe HTML (careful!): <script>alert("dangerous!")</script>
Truncated text: This is a very...

Control Structures

Conditionals

Jinja2 allows you to use if statements to conditionally render content:

html
{% if user.is_admin %}
<p>Welcome, Admin!</p>
{% elif user.is_authenticated %}
<p>Welcome back, {{ user.name }}!</p>
{% else %}
<p>Please log in to continue.</p>
{% endif %}

Loops

You can iterate over collections like lists, dictionaries, or any iterable:

html
<h2>Users:</h2>
<ul>
{% for user in users %}
<li>{{ user.name }} ({{ user.email }})</li>
{% else %}
<li>No users found.</li>
{% endfor %}
</ul>

The else clause is executed if the collection is empty.

Useful loop variables are available inside a for loop:

html
<ol>
{% for item in items %}
<li class="{{ loop.cycle('odd', 'even') }}">
{{ loop.index }}. {{ item }}
{% if loop.first %}(first item!){% endif %}
{% if loop.last %}(last item!){% endif %}
</li>
{% endfor %}
</ol>

Here's a Flask example with loops and conditionals:

python
@app.route('/users')
def users_list():
users = [
{'name': 'Alice', 'is_active': True, 'role': 'admin'},
{'name': 'Bob', 'is_active': True, 'role': 'user'},
{'name': 'Charlie', 'is_active': False, 'role': 'user'}
]
return render_template('users.html', users=users)

And the template:

html
<h2>User List</h2>

<table border="1">
<tr>
<th>#</th>
<th>Name</th>
<th>Status</th>
<th>Role</th>
</tr>
{% for user in users %}
<tr class="{{ 'inactive' if not user.is_active else '' }}">
<td>{{ loop.index }}</td>
<td>{{ user.name }}</td>
<td>
{% if user.is_active %}
<span style="color: green;">Active</span>
{% else %}
<span style="color: red;">Inactive</span>
{% endif %}
</td>
<td>{{ user.role|capitalize }}</td>
</tr>
{% else %}
<tr>
<td colspan="4">No users found</td>
</tr>
{% endfor %}
</table>

Template Inheritance

One of Jinja2's most powerful features is template inheritance. It allows you to define a base template with common elements and extend it in child templates.

First, create a base template (base.html):

html
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Default Title{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
{% block extra_css %}{% endblock %}
</head>
<body>
<header>
<nav>
<ul>
<li><a href="{{ url_for('index') }}">Home</a></li>
<li><a href="{{ url_for('about') }}">About</a></li>
<li><a href="{{ url_for('contact') }}">Contact</a></li>
</ul>
</nav>
</header>

<main>
{% block content %}
{% endblock %}
</main>

<footer>
<p>&copy; {{ current_year }} My Flask App</p>
</footer>

<script src="{{ url_for('static', filename='app.js') }}"></script>
{% block extra_js %}{% endblock %}
</body>
</html>

Then, create child templates that extend the base:

html
{% extends "base.html" %}

{% block title %}Home Page{% endblock %}

{% block content %}
<h1>Welcome to our website!</h1>
<p>This is the home page content.</p>
{% endblock %}

Another child template:

html
{% extends "base.html" %}

{% block title %}About Us{% endblock %}

{% block content %}
<h1>About Us</h1>
<p>Learn more about our company history.</p>

{% block company_info %}
<h2>Company Information</h2>
<p>Founded in 2023</p>
{% endblock %}
{% endblock %}

{% block extra_css %}
<link rel="stylesheet" href="{{ url_for('static', filename='about.css') }}">
{% endblock %}

Let's see how to implement this in Flask:

python
from datetime import datetime

@app.route('/')
def index():
return render_template('index.html', current_year=datetime.now().year)

@app.route('/about')
def about():
return render_template('about.html', current_year=datetime.now().year)

@app.route('/contact')
def contact():
return render_template('contact.html', current_year=datetime.now().year)

Including Templates

You can include other templates using the include statement:

html
<div class="sidebar">
{% include 'partials/sidebar.html' %}
</div>

<div class="main-content">
<h1>Main Content</h1>
<p>Some content here...</p>

{% include 'partials/user_info.html' with context %}
</div>

Using the with context option passes the current context to the included template.

Macros

Macros are similar to functions in programming languages. They allow you to define reusable snippets of code:

html
{% macro input_field(name, label, value='', type='text') %}
<div class="form-group">
<label for="{{ name }}">{{ label }}</label>
<input type="{{ type }}" id="{{ name }}" name="{{ name }}" value="{{ value }}">
</div>
{% endmacro %}

<form method="post">
{{ input_field('username', 'Username') }}
{{ input_field('password', 'Password', type='password') }}
{{ input_field('remember', 'Remember me', type='checkbox') }}
<button type="submit">Login</button>
</form>

You can also import macros from other templates:

html
{% import 'macros.html' as forms %}

<form method="post">
{{ forms.input_field('username', 'Username') }}
{{ forms.input_field('password', 'Password', type='password') }}
<button type="submit">Login</button>
</form>

Real-World Example: Blog Application

Let's combine all these concepts into a small blog application:

python
from flask import Flask, render_template, request, redirect, url_for
from datetime import datetime

app = Flask(__name__)

# Simulated database
posts = [
{
'id': 1,
'title': 'Getting Started with Flask',
'content': 'Flask is a micro web framework written in Python...',
'author': 'Alice',
'date': datetime(2023, 1, 15),
'tags': ['flask', 'python', 'web']
},
{
'id': 2,
'title': 'Jinja2 Templating',
'content': 'Jinja2 is a modern and designer-friendly templating language...',
'author': 'Bob',
'date': datetime(2023, 2, 10),
'tags': ['jinja2', 'templates', 'flask']
}
]

@app.route('/')
def index():
return render_template('blog/index.html',
posts=posts,
current_year=datetime.now().year)

@app.route('/post/<int:post_id>')
def post_detail(post_id):
post = next((p for p in posts if p['id'] == post_id), None)
if post is None:
return render_template('blog/not_found.html'), 404
return render_template('blog/post_detail.html',
post=post,
current_year=datetime.now().year)

if __name__ == '__main__':
app.run(debug=True)

Base template (templates/blog/base.html):

html
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}My Blog{% endblock %}</title>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; max-width: 800px; margin: 0 auto; padding: 20px; }
header { border-bottom: 1px solid #eee; margin-bottom: 20px; }
.post { margin-bottom: 30px; }
.post-meta { color: #666; font-size: 0.9em; }
.tag { background: #eee; padding: 3px 8px; border-radius: 3px; font-size: 0.8em; margin-right: 5px; }
</style>
</head>
<body>
<header>
<h1><a href="{{ url_for('index') }}">My Flask Blog</a></h1>
</header>

<main>
{% block content %}{% endblock %}
</main>

<footer>
<p>&copy; {{ current_year }} My Flask Blog</p>
</footer>
</body>
</html>

Index template (templates/blog/index.html):

html
{% extends "blog/base.html" %}

{% block title %}Home - My Blog{% endblock %}

{% block content %}
<h2>Latest Posts</h2>

{% for post in posts %}
<div class="post">
<h3><a href="{{ url_for('post_detail', post_id=post.id) }}">{{ post.title }}</a></h3>
<div class="post-meta">
Posted by {{ post.author }} on {{ post.date.strftime('%B %d, %Y') }}
</div>
<p>{{ post.content|truncate(100) }}</p>
<div>
{% for tag in post.tags %}
<span class="tag">{{ tag }}</span>
{% endfor %}
</div>
</div>
{% else %}
<p>No posts found.</p>
{% endfor %}
{% endblock %}

Post detail template (templates/blog/post_detail.html):

html
{% extends "blog/base.html" %}

{% block title %}{{ post.title }} - My Blog{% endblock %}

{% block content %}
<div class="post">
<h2>{{ post.title }}</h2>
<div class="post-meta">
Posted by {{ post.author }} on {{ post.date.strftime('%B %d, %Y') }}
</div>

<div class="post-content">
{{ post.content }}
</div>

<div class="tags">
{% for tag in post.tags %}
<span class="tag">{{ tag }}</span>
{% endfor %}
</div>

<p><a href="{{ url_for('index') }}">Back to posts</a></p>
</div>
{% endblock %}

Not found template (templates/blog/not_found.html):

html
{% extends "blog/base.html" %}

{% block title %}Not Found - My Blog{% endblock %}

{% block content %}
<h2>Post Not Found</h2>
<p>The post you're looking for doesn't exist.</p>
<p><a href="{{ url_for('index') }}">Back to posts</a></p>
{% endblock %}

Best Practices

  1. Keep Logic Minimal: Templates should contain minimal logic. Complex calculations should be done in your Flask routes.

  2. Use Template Inheritance: Create a base template and extend it to avoid duplicating code.

  3. Auto-escape By Default: Jinja2 escapes variables by default (security feature). Use {{ var|safe }} only when you're sure the content is safe.

  4. Create Reusable Components: Use macros for components you'll reuse across your site.

  5. Organize Templates: Structure your templates in directories based on functionality (e.g., auth/, blog/).

  6. Comment Your Templates: Use {# This is a comment #} for complex sections.

Common Issues and Solutions

  1. Undefined variables: Use the default filter or conditional rendering:

    html
    {{ variable|default('Default Value') }}
  2. HTML escaping issues: When you need to render HTML content:

    html
    {{ html_content|safe }}
  3. Debugging: Enable Flask's debug mode (app.run(debug=True)) to see template errors.

Summary

Jinja2 templates in Flask provide a powerful way to create dynamic websites while maintaining clean separation between logic and presentation. In this guide, we covered:

  • Basic variable expressions and delimiters
  • Control structures (if statements and loops)
  • Template inheritance and includes
  • Macros for reusable components
  • Filters for transforming data
  • Best practices for template design

With these fundamentals, you can create sophisticated web applications that are both maintainable and secure.

Additional Resources

Exercises

  1. Create a template that displays a list of products with categories, using loops and conditionals to format them differently based on availability.

  2. Implement a base template with navigation, then create three pages that extend this base: Home, About, and Contact.

  3. Create a macro for generating HTML form fields, then use it to build a registration form.

  4. Write a template that uses filters to format dates, numbers, and text from a dataset.

  5. Implement a simple blog template system with inheritance and includes, displaying post lists and individual posts.

Happy templating!



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)