Skip to main content
Flask Basics Tutorial
CHAPTER 06 Beginner

Flask Templates with Jinja2

Updated: May 14, 2026
30 min read

# CHAPTER 6

Flask Templates with Jinja2

1. Introduction

Returning <h1>Hello</h1> from a Python function works for a single line of text, but building a real website with hundreds of lines of HTML inside a Python string is a maintenance nightmare. To solve this, we must separate our presentation layer from our logic layer. In this chapter, we will use the rendertemplate function to return physical HTML files. We will also explore Jinja2, the powerful templating engine included with Flask, which allows us to inject Python variables, execute loops, and use template inheritance to eliminate duplicate HTML code.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Configure the templates directory in a Flask project.
  • Use rendertemplate to return HTML files to the browser.
  • Inject Python variables into HTML using Jinja2 {{ }} tags.
  • Execute Python logic in HTML using Jinja2 {% %} tags.
  • Implement Template Inheritance (Master Skeletons).

3. Beginner-Friendly Explanation

Imagine a printing press that makes personalized birthday cards. If you don't use templates, you have to carve an entirely new wooden printing block for every single person (Writing HTML inside Python strings). If you use Jinja2 Templates, you carve one single Master Block that says: "Happy Birthday, [NAMEGOESHERE]!" When the printing press runs, Python simply passes a list of names to the machine. The machine (Jinja2) quickly stamps "Alice", then "Bob", then "Charlie" into the blank spaces, printing thousands of personalized cards in seconds from a single master layout.

4. Step 1: The Templates Directory

Flask is highly opinionated about where HTML files live. You MUST create a folder named templates in the exact same directory as your app.py file. If the folder is named incorrectly, Flask will crash.
text
1234
my_flask_app/
    app.py
    templates/       <-- Create this folder!
        home.html    <-- Create this file!

Create templates/home.html:

html
1234567
<!DOCTYPE html>
<html>
<head><title>Flask App</title></head>
<body>
    <h1>Welcome to the Homepage</h1>
</body>
</html>

5. Step 2: Rendering the Template

Open app.py. We must import a new function called render_template.
python
1234567891011
from flask import Flask, render_template

app = Flask(__name__)

@app.route(&#039;/')
def home():
    # Flask automatically looks inside the 'templates' folder for this file!
    return render_template(&#039;home.html')

if __name__ == &#039;__main__':
    app.run(debug=True)

6. Step 3: Injecting Dynamic Data (Jinja2 Variables)

HTML is static. It cannot calculate math or read databases. Jinja2 bridges this gap. We can pass variables from Python into the HTML using keyword arguments.

In app.py:

python
1234
@app.route(&#039;/profile/<username>')
def profile(username):
    # Pass the 'username' variable to the template
    return render_template(&#039;profile.html', user=username, age=25)

In templates/profile.html: Use the double-curly brace syntax {{ }} to print the variable:

html
123
<h1>Profile Page</h1>
<p>Name: {{ user }}</p>
<p>Age: {{ age }}</p>

7. Step 4: Jinja2 Logic (Loops and If Statements)

What if we have a list of blog posts? We can't write 50 HTML paragraphs. We use Jinja2 logic tags {% %} to write Python loops directly inside the HTML.

In app.py:

python
1234
@app.route(&#039;/blog')
def blog():
    posts_list = [&#039;First Post', 'Second Post', 'Third Post']
    return render_template(&#039;blog.html', posts=posts_list)

In templates/blog.html:

html
123456789101112131415
<h1>Latest Posts</h1>
<ul>
    <!-- Start the loop -->
    {% for post in posts %}
        <li>{{ post }}</li>
    <!-- You MUST explicitly end the loop! -->
    {% endfor %}
</ul>

<!-- If Statements work too! -->
{% if posts|length > 0 %}
    <p>We have articles available!</p>
{% else %}
    <p>No articles yet.</p>
{% endif %}

8. Backend Workflow: Template Inheritance

Every page on your site needs a navigation bar and a footer. Copy-pasting this code into every file is terrible practice. We use Inheritance.

Create a Master Skeleton: templates/base.html

html
1234567891011
<!DOCTYPE html>
<html>
<body>
    <nav> <a href="/">Home</a> | <a href="/blog">Blog</a> </nav>
    
    <!-- This is a placeholder! Child templates will inject data here -->
    {% block content %}{% endblock %}
    
    <footer>Copyright 2026</footer>
</body>
</html>

Rewrite the Child: templates/blog.html

html
12345678910
<!-- 1. Inherit from the Master Skeleton -->
{% extends "base.html" %}

<!-- 2. Target the specific placeholder block -->
{% block content %}
    <h1>Latest Posts</h1>
    <ul>
        {% for post in posts %}<li>{{ post }}</li>{% endfor %}
    </ul>
{% endblock %}

*Notice how clean the child template is? It contains no <html> or <body> tags! If you want to change the navbar, you only change base.html, and every page on the site updates instantly.*

9. Best Practices

  • Generating URLs dynamically: Never hardcode URLs in your HTML (<a href="/about">). If you change the URL path in Python, every HTML link will break. Instead, use Jinja2 and the urlfor() function: <a href="{{ urlfor('about') }}">. This dynamically generates the correct URL based on the Python function name!

10. Common Mistakes

  • TemplateNotFound Error: If you see this error, you either misspelled the file name in rendertemplate(), OR you named your folder template instead of templates. Flask strictly looks for the plural templates folder.

11. Exercises

  1. 1. Explain the difference in usage between the {{ }} syntax and the {% %} syntax in Jinja2 templates.

12. Coding Challenges

  • Challenge: Create a new route /store. In the View function, create a Python dictionary representing a product: item = {'name': 'Laptop', 'price': 999}. Pass this dictionary to a new template named store.html. In the template, use Jinja2 to display the name and price in an <h2> tag.

13. MCQs with Answers

Question 1

Which Flask function is used to combine an HTTP request with an HTML file and dynamic variables to return a finished web page?

Question 2

When using Jinja2 Template Inheritance, which tag is used in the child template to declare that it is building upon a master skeleton template?

14. Interview Questions

  • Q: Explain the concept of the "Context Dictionary" (keyword arguments) in a Flask View. How does data travel from the Python Controller into the final HTML output?
  • Q: Why do modern web frameworks enforce a restriction against executing complex logic (like database queries) directly inside Template files? What architectural principle does this protect?

15. FAQs

Q: Can I use React or Vue.js instead of Jinja2? A: Absolutely! If you are building an API, you skip render
template entirely and just return JSON data. Your React frontend will consume that data. However, for many traditional applications, Jinja2 is significantly faster to develop.

16. Summary

In Chapter 6, we separated our presentation layer from our logic layer. By creating a templates directory and utilizing the render_template() function, we successfully returned dedicated HTML files to the browser. We mastered Jinja2 syntax, using {{ }} to inject dynamic Python variables and {% %} to execute loops. Finally, we implemented Template Inheritance, solving the problem of duplicate HTML code and paving the way for a scalable frontend architecture.

17. Next Chapter Recommendation

Currently, data only flows from the server to the user. We need data to flow from the user to the server. Proceed to Chapter 7: Handling Forms and User Input.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·