Templates with Jinja2
Reviewed & published by Brayan K
A template is an HTML file with placeholders that Flask fills in with your data at request time — Flask's Jinja2 engine lets you keep your HTML in separate files and inject Python values into them with render_template.
Part of the free Flask course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
In this lesson you'll render template files, pass variables into them, and use Jinja2 loops and conditionals to build pages from data. Run the examples locally with flask run.
What You'll Learn in This Lesson
- Why templates beat inline HTML strings
- Where the templates/ folder lives
- Rendering a file with render_template
- Passing variables into a template
- Outputting values with the {{ }} delimiter
- Loops and conditionals with {% %}
1 From Inline HTML to render_template
So far your view functions returned HTML as a Python string. That gets messy fast. The better way is to keep HTML in its own file and ask Flask to render it. Flask uses the Jinja2 template engine and a helper called render_template.
Flask looks for template files inside a folder named templates/ sitting next to your app file. A project might look like this:
myapp/
├── app.py
└── templates/
└── index.htmlYou then render index.html by name. The Python below imports render_template and returns a rendered file instead of a string:
# app.py — render an HTML file instead of a string
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def home():
# Flask finds templates/index.html and renders it.
return render_template("index.html")
# templates/index.html would contain, for example:
# <h1>Welcome!</h1>
# <p>This page came from a template file.</p>
print("Home route renders templates/index.html")
print("Run with: flask run -> http://127.0.0.1:5000")
# ✅ Expected output:
# Home route renders templates/index.html
# Run with: flask run -> http://127.0.0.1:50002 Passing Variables and Outputting Them
Templates become powerful when you pass data into them. You send variables as keyword arguments to render_template, and inside the template you display a value with the double-brace delimiter {{ ... }}. For example {{ name }} prints whatever you passed as name.
The Python below passes name and visits into a template. The accompanying HTML uses {{ name }} and {{ visits }} to drop those values into the page:
# Passing variables into a template
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/hello/<name>")
def hello(name):
# Keyword args become variables inside the template.
return render_template("hello.html", name=name, visits=3)
# templates/hello.html:
# <h1>Hello, {{ name }}!</h1>
# <p>You have visited {{ visits }} times.</p>
# What Jinja2 produces for /hello/Ada:
name, visits = "Ada", 3
print(f"<h1>Hello, {name}!</h1>")
print(f"<p>You have visited {visits} times.</p>")
# ✅ Expected output:
# <h1>Hello, Ada!</h1>
# <p>You have visited 3 times.</p>In prose we write the output delimiter as {{ name }}, but inside an actual .html template file you type it normally. Jinja2 replaces it with the value at render time.
3 Loops and Conditionals
Jinja2 also runs logic. While {{ ... }} prints a value, the {% ... %} delimiter controls flow — loops and conditionals. To loop you write {% for item in items %} and close it with {% endfor %}. For conditions you use {% if condition %} and {% endif %}.
The Python below passes a list of tasks into a template. The HTML loops over them and shows a message when the list is empty:
<!-- templates/todo.html -->
<h1>To-Do List</h1>
{% if tasks %}
<ul>
{% for task in tasks %}
<li>{{ task }}</li>
{% endfor %}
</ul>
{% else %}
<p>Nothing to do. Relax!</p>
{% endif %}# Looping over data in a template
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/todos")
def todos():
tasks = ["Learn routes", "Read request.form", "Write a template"]
return render_template("todo.html", tasks=tasks)
# What the Jinja2 loop produces:
tasks = ["Learn routes", "Read request.form", "Write a template"]
print("<h1>To-Do List</h1>")
if tasks:
print("<ul>")
for task in tasks:
print(f" <li>{task}</li>")
print("</ul>")
else:
print("<p>Nothing to do. Relax!</p>")
# ✅ Expected output:
# <h1>To-Do List</h1>
# <ul>
# <li>Learn routes</li>
# <li>Read request.form</li>
# <li>Write a template</li>
# </ul>🔬 Jinja2 Syntax at a Glance
| Syntax | What it does |
|---|---|
| {{ name }} | Outputs the value of name |
| {% for x in items %} | Starts a loop (close with {% endfor %}) |
| {% if cond %} | Starts a conditional (close with {% endif %}) |
| {# note #} | A comment that is not rendered |
🎯 Your Turn: Fill in the Blanks
Finish the view so it renders a template and passes a username into it. Replace each ___ in the Python.
# 🎯 YOUR TURN: replace each ___ to render a template
from flask import Flask, ___ # what do we import to render files?
app = Flask(__name__)
@app.route("/welcome/<username>")
def welcome(username):
# Render templates/welcome.html and pass the name in
return ___("welcome.html", username=___)
# templates/welcome.html would contain:
# <h1>Welcome, {{ username }}!</h1>
print("welcome route ready")Common Errors & Quick Reference
❌ jinja2.exceptions.TemplateNotFound: index.html
Flask couldn't find the file. Make sure it's inside a folder named templates/ next to your app file, and that the name you passed matches exactly.
❌ A variable shows up blank in the page
You used {{ name }} in the template but didn't pass name to render_template. Add it as a keyword argument, e.g. render_template("p.html", name="Ada").
| Task | Code |
|---|---|
| Import the helper | from flask import render_template |
| Render a file | render_template("index.html") |
| Pass a variable | render_template("p.html", x=1) |
| Output it | {{ x }} |
🏆 Mini Challenge: A Product List Page
Render a page that lists products from data.
- Pass a list of product names into a template
- Use {% for product in products %} to loop over them
- Output each one with {{ product }} inside a list item
# 🏆 Mini Challenge: a product list page
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/products")
def products():
items = ["Keyboard", "Mouse", "Monitor"]
return render_template("products.html", products=items)
# templates/products.html:
# <h1>Products</h1>
# <ul>
# {% for product in products %}
# <li>{{ product }}</li>
# {% endfor %}
# </ul>
# What Jinja2 would output:
products = ["Keyboard", "Mouse", "Monitor"]
print("<h1>Products</h1>")
print("<ul>")
for product in products:
print(f" <li>{product}</li>")
print("</ul>")❓ Frequently Asked Questions
Lesson complete — your pages come from templates now!
You can render HTML files with render_template, pass variables into them, output values with {{ }}, and run loops and conditionals with {% %}. Your HTML now lives where it belongs — in template files.
Practice quiz
Which template engine does Flask use by default?
- Mako
- Handlebars
- Jinja2
- Django Templates
Answer: Jinja2. Flask renders templates with the Jinja2 engine.
Which function renders a template file from a view?
- render_template()
- send_template()
- template()
- render()
Answer: render_template(). render_template('index.html') loads and renders a file from the templates folder.
Where does Flask look for template files?
- A folder named views/
- A folder named templates/
- The static/ folder
- The project root
Answer: A folder named templates/. Flask looks for templates in a folder named 'templates' next to your app file.
Which Jinja2 delimiter outputs a value?
- {# value #}
- [[ value ]]
- {% value %}
- {{ value }}
Answer: {{ value }}. Double curly braces {{ }} print a value into the page.
Which delimiter runs logic such as loops and conditionals?
- {% ... %}
- {{ ... }}
- {# ... #}
- <% ... %>
Answer: {% ... %}. The {% ... %} delimiter controls flow, for example {% for %} loops and {% if %} conditionals.
How do you pass a variable into a template?
- By global assignment
- As a keyword argument to render_template
- Via request.form
- Through a config key
Answer: As a keyword argument to render_template. You pass keyword arguments like render_template('hi.html', name='Ada') and reference them by name.
Which tag closes a {% for %} loop in Jinja2?
- {% endloop %}
- {% next %}
- {% endfor %}
- {% done %}
Answer: {% endfor %}. Every {% for %} must be closed with a matching {% endfor %}.
What does {# note #} do in a template?
- Outputs the note text
- Starts a loop
- Defines a variable
- Adds a comment that is not rendered
Answer: Adds a comment that is not rendered. {# ... #} is a Jinja2 comment and never appears in the rendered page.
A TemplateNotFound error usually means what?
- The file is not in the templates/ folder or the name is wrong
- The variable was not passed
- The secret key is missing
- Debug mode is off
Answer: The file is not in the templates/ folder or the name is wrong. Flask could not find the file; it must be inside templates/ and the name must match exactly.
Why use templates instead of returning HTML strings?
- They render faster than strings always
- They keep HTML separate from Python code
- They disable caching
- They require no folder
Answer: They keep HTML separate from Python code. Templates keep your HTML in its own files, separate from your Python logic, which scales much better.
Continue this course
- Previous: HTTP Methods (GET & POST)
- Next: Static Files (CSS, JS, Images) — Serve stylesheets, scripts, and images from the static folder
- Quick reference: Flask cheat sheet › Templates (Jinja2)
Frequently asked questions
What is a template in Flask?
A template is an HTML file with placeholders that Flask fills in with data at request time. Flask uses the Jinja2 engine and renders templates with the render_template function, keeping HTML separate from your Python code.
Where do template files go?
Flask looks for templates in a folder named 'templates' next to your app file. Put files like index.html inside that folder and render them by name with render_template('index.html').
What is the difference between the two Jinja2 delimiters?
Double curly braces output a value, like inserting a variable into the page. The brace-percent form runs logic such as loops and conditionals. One prints, the other controls flow.
How do I pass data into a template?
Pass keyword arguments to render_template, for example render_template('hi.html', name='Ada'). Inside the template you then reference that variable by name to display it.