Skip to content

Passing Variables to Templates

Templates become powerful when you pass data to them.

python
from flask import Flask, render_template
 
app = Flask(__name__)
 
 
@app.route("/hello/<name>")
def hello(name):
    return render_template("hello.html", name=name)

Template templates/hello.html:

html
<!doctype html>
<html>
  <body>
    <h1>Hello, {{ name }}!</h1>
  </body>
</html>
python
@app.route("/users")
def users():
    data = [
        {"username": "ravi", "role": "admin"},
        {"username": "alex", "role": "user"},
    ]
    return render_template("users.html", users=data)

In Jinja you can access:

  • {{ user.username }} or {{ user['username'] }}
  • Keep templates focused on presentation.
  • Do validation/computation in Python, then pass already-clean data to the template.

This makes templates simpler and safer.

This is the behaviour that makes template bugs so hard to see. A name that does not exist renders as nothing at all:

diagram Diagram mermaid

Measured:

undefined.py
render_template_string("start[{{ nope }}]end")
# 'start[]end'                       <- silent
 
render_template_string("{% if missing %}yes{% else %}no{% endif %}")
# 'no'                               <- undefined is falsy
 
render_template_string("{{ nope.attr }}")
# jinja2.exceptions.UndefinedError: 'nope' is undefined

So a typo in a variable name produces a blank space on the page, while a typo in an attribute produces a loud error. The first is the one that reaches production.

What is already available without passing it

Section titled “What is already available without passing it”

Flask injects a few names into every template context:

namewhat it is
url_forbuild a URL from an endpoint name
requestthe current request object
sessionthe session dict
gthe per-request global object
configthe app config
views.py
return render_template("profile.html", user=user, posts=posts)
profile.html
<h1>{{ user.name }}</h1>
{% for p in posts %}
  <a href="{{ url_for('post', id=p.id) }}">{{ p.title }}</a>
{% endfor %}

To add your own name to every template without passing it each time, use a context processor:

context.py
@app.context_processor
def inject_year():
    return {"year": datetime.now().year}       # {{ year }} now works everywhere
sketch What happens to a name the context does not have p5.js
An undefined name prints as empty and is falsy, but touching an attribute on it raises. The quiet case is the dangerous one.
pch.quizTag pch.quizDefaultTitle
  1. A template contains {{ usr }} but the view passed user=. What is rendered?

    pch.quizShowAnswer

    C — an empty string, with no error or warning — Undefined renders as empty. Measured: 'start[{{ nope }}]end' gave 'start[]end'. A typo in a variable name becomes a blank space, which is why it reaches production.

  2. Why does {{ nope }} render silently while {{ nope.attr }} raises?

    pch.quizShowAnswer

    B — printing Undefined is defined as empty, but getting an attribute from Undefined is an error — Undefined has a string form (empty) and is falsy, but attribute access on it raises UndefinedError. Set app.jinja_env.undefined = StrictUndefined to make the quiet case loud in development.

  3. Which name is available in every template without being passed by the view?

    pch.quizShowAnswer

    B — url_for — Flask injects url_for, request, session, g and config. Anything else must be passed to render_template or added with a @app.context_processor.

pch.coffeeTagline

pch.coffeeCta

pch.feedbackHeading

pch.feedbackSubheading