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

Sponsored

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 .html file 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:

  1. Create a templates folder in your project root (Flask looks here by default).
  2. Create an HTML template file, e.g., hello.html, with placeholders like {{ name }}.
  3. In your route, import render_template from flask.
  4. Call render_template('hello.html', name=my_variable) — pass the template name and any data as keyword arguments.
  5. 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 your app.py.
  • Fix: Check your project structure and ensure the file exists. If you’re running from a different directory, set the template_folder explicitly on your Flask app.

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 passing my_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 |e filter — but be careful, this can introduce XSS vulnerabilities if the data isn’t trusted.
  • Fix: Only use |safe on data you control, or better, use markupsafe.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 raise TemplateNotFound.
  • Using locals() or globals() 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 |safe filter on user input, which introduces XSS vulnerabilities.

Variations

  1. Use a context processor (@app.context_processor) to inject variables like current_user into every template.
  2. Pass a dictionary and unpack it with render_template('page.html', **data) for a tidy syntax.
  3. Store template data in a global g object 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; configure template_folder only if you must.
  • Avoid locals() and be careful with |safe — they can leak or inject unwanted content.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.