Pass Data from Flask to Templates
Pass data from Flask to templates — Python web development tutorial, lesson 14. Learn the core concept, see how it works step by step, and apply it in a hands-on exercise. Includes troubleshooting and what to study next.
Focus: pass data from flask to templates
You’ve built a Flask route that returns a plain string, and it works. But the moment you want a real page — with a user’s name, a list of products, or yesterday’s metrics — hard-coding that HTML in a string becomes a nightmare. You need a clean way to pass data from Flask to templates, turning your Python variables into dynamic, readable web pages. This lesson shows you exactly how to do it with Jinja2, Flask’s built-in template engine, so you can stop fighting with string concatenation and start building pages that feel alive.
The Problem This Lesson Solves
Every Flask app eventually needs to show data that changes: a logged-in user’s name, blog posts pulled from a database, or scores from an API. If you try to build that HTML in Python, you end up with messy, unreadable code like this:
@app.route("/user")
def user():
name = "Ada"
return f"<h1>Hello {name}!</h1>" # This gets ugly fast
That approach breaks down as soon as you have more than one variable, any conditionals, or a loop. You lose the separation between logic (Python) and presentation (HTML). The pain is real: your code becomes a tangled mess of escaped quotes and nested braces, and any designer who needs to touch the HTML has to wade through Python. The fix is to pass data from Flask to templates, letting your template engine render the HTML while your Python stays clean and focused on logic.
Core Concept / Mental Model
Think of Flask as a factory and your template as a blueprint. The blueprint is what the page looks like, but it has empty slots for the details. Your job is to fill those slots with data.
- Flask route – the Python function that handles a request.
render_template()– the function that loads an HTML template and injects data into it.- Jinja2 template – the
.htmlfile with special placeholders like{{ variable }}and{% for %}loops.
Here’s the mental model: the route is the brain, the template is the body. The brain decides what data to show, and the body decides how to present it. You pass data from Flask to templates by giving render_template() keyword arguments — each keyword becomes a variable available inside the template.
For example, if you pass name="Ada", then inside the template {{ name }} becomes Ada. Simple, right? This separation lets you change the look of your page without touching Python and change the data without touching HTML.
How It Works Step by Step
Let’s see the exact steps to pass data from Flask to templates:
- Create a templates folder in your project root (Flask looks here by default).
- Create an HTML template file, e.g.,
hello.html, with placeholders like{{ name }}. - In your route, import
render_templatefromflask. - Call
render_template('hello.html', name=my_variable)— pass the template name and any data as keyword arguments. - Inside the template, use the variable names you passed — e.g.,
{{ name }}— to display the data.
That’s the whole process. Flask takes your data, renders the template, and sends the resulting HTML to the client.
Where Does Flask Look for Templates?
By default, Flask expects a templates/ folder inside your application root. For a simple app, the structure looks like:
project/
├── app.py
└── templates/
└── index.html
If you keep your templates elsewhere, you can customize the template_folder parameter in your Flask instance, but sticking to the default keeps things predictable.
Hands-On Walkthrough
Time to get your hands dirty. We’ll build a tiny app that greets a user and shows a short list of items.
1. Minimal Flask App
First, create a file app.py with the basic structure:
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def home():
name = "Ada"
return render_template("index.html", name=name)
if __name__ == "__main__":
app.run(debug=True)
Notice how we pass name as a keyword argument. Inside the template, that name is now available.
2. The Template
Create templates/index.html with:
<!DOCTYPE html>
<html>
<head>
<title>Hello</title>
</head>
<body>
<h1>Hello, {{ name }}!</h1>
</body>
</html>
Now run python app.py and visit http://127.0.0.1:5000/. You’ll see “Hello, Ada!” — your data made it from Python to the template.
3. Passing Multiple Variables and Lists
Let’s expand the example to pass several data types, including a list:
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def home():
user = {"name": "Ada", "age": 36}
items = ["Terminal", "Editor", "Browser"]
return render_template("index.html", user=user, items=items)
And in templates/index.html:
<!DOCTYPE html>
<html>
<head>
<title>User Dashboard</title>
</head>
<body>
<h1>Hello, {{ user["name"] }} — you are {{ user["age"] }}.</h1>
<p>Your tools:</p>
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
</ul>
</body>
</html>
Output: a heading with Ada’s name and age, and an unordered list with each item. Passing multiple variables is as easy as adding more arguments — no limits on type either: strings, integers, lists, dictionaries, even custom objects.
4. Using Conditions in Templates
Jinja2 also lets you use logic, like if statements, directly in the template:
@app.route("/status")
def status():
logged_in = True
return render_template("status.html", logged_in=logged_in)
<p>
{% if logged_in %}
Welcome back!
{% else %}
Please log in.
{% endif %}
</p>
This keeps control flow in the view layer while your Python stays pure data and business logic.
Compare Options / When to Choose What
There are a few ways to pass data to templates in Flask. Here’s a quick comparison:
| Method | Use case | Pros | Cons |
|---|---|---|---|
render_template('page.html', **data) |
Most common; any kind of variable | Clean, explicit, readable | You must list every variable |
render_template('page.html', **locals()) |
Quick-and-dirty when you have many variables | No repetition | Passes everything including internals — risky |
Template globals via app.context_processor |
Data needed in every template (like logged-in user) | Always available | Less explicit; harder to track |
Using g or session |
Data tied to the request | Feels natural | Template can’t see them directly; you still need a context processor |
For most cases, the explicit keyword argument approach is the cleanest and easiest to debug. Use a context processor only when you need the same data everywhere, like a cart count or a current user object. Avoid locals() in production — it can accidentally pass secrets or unnecessary data.
Troubleshooting & Edge Cases
Even simple template rendering can trip you up. Here are classic issues and their fixes:
1. TemplateNotFound
- Error:
jinja2.exceptions.TemplateNotFound: index.html - Cause: Flask can’t find your template. Either the file isn’t in the
templates/folder, or the folder isn’t next to yourapp.py. - Fix: Check your project structure and ensure the file exists. If you’re running from a different directory, set the
template_folderexplicitly on yourFlaskapp.
2. Undefined variables in template
- Symptom: The page shows nothing where you expected data.
- Cause: You referenced a variable that wasn’t passed to
render_template(). - Fix: Use
{{ my_var }}only after passingmy_var=...in the route. If you need it to be optional, use{{ my_var or '' }}or a default.
3. Escaping issues
- Symptom: HTML tags appear as text, or the page looks broken.
- Cause: Jinja2 auto-escapes special characters by default. If you intend to display raw HTML, you must use
{{ data | safe }}or the|efilter — but be careful, this can introduce XSS vulnerabilities if the data isn’t trusted. - Fix: Only use
|safeon data you control, or better, usemarkupsafe.Markup.
4. Passing Python objects with special methods
- Symptom: Object attributes don’t show up as expected.
- Cause: Jinja2 allows attribute access with dot notation, but it can conflict with dictionary keys.
- Fix: Use
{{ user["name"] }}for dicts and{{ user.name }}for objects. If the attribute is a method call with arguments, you’ll need to compute it in Python first.
What You Learned & What’s Next
You now know how to pass data from Flask to templates: you understand the core idea of separating logic and presentation, you can use render_template() with keyword arguments to inject variables, and you can loop through lists and use conditionals inside Jinja2 templates. You’ve also seen how to avoid common pitfalls like missing template files and undefined variables.
This is a foundational skill for every Flask app — next, you’ll want to explore template inheritance to build consistent layouts, or learn how to handle forms and user input so you can send data back from templates to Flask. That’s step 15 in this track: Handling form submissions with Flask-WTF. Get ready to make your pages interactive.
Practice recap
Create a simple Flask app with a route that passes a list of your favorite books to a template. Use a {% for %} loop to display each book title in an unordered list. Then add a conditional to show 'Read' or 'Unread' based on a boolean field. This exercises all the core concepts from this lesson.
Common mistakes
- Forgetting to put templates in the
templates/folder — Flask will raiseTemplateNotFound. - Using
locals()orglobals()to pass every variable — it may expose sensitive data and hurts readability. - Trying to pass a list directly as a string — always read it with
{% for %}in the template, not with{{ list }}(which shows the Python repr). - Over-using
|safefilter on user input, which introduces XSS vulnerabilities.
Variations
- Use a context processor (
@app.context_processor) to inject variables likecurrent_userinto every template. - Pass a dictionary and unpack it with
render_template('page.html', **data)for a tidy syntax. - Store template data in a global
gobject and access it in a context processor for request-scoped values.
Real-world use cases
- A personal blog that shows posts with titles, dates, and content by passing a list of post objects to the template.
- An e-commerce product page that passes a product dictionary (name, price, description) to render dynamic HTML.
- A dashboard that displays user-specific metrics (like sales totals) by passing a dict of values to a chart-friendly template.
Key takeaways
- Use
render_template()with keyword arguments to pass data from Flask to templates. - Keep Python logic in routes and presentation logic (loops, conditionals) in Jinja2 templates.
- Jinja2 syntax:
{{ variable }}for output,{% for %}and{% if %}for flow control. - Always keep templates in a
templates/folder by default; configuretemplate_folderonly if you must. - Avoid
locals()and be careful with|safe— they can leak or inject unwanted content.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.