Introduction to Templating
Flask uses the Jinja2 template engine to render dynamic content. This integration allows you to separate your application logic from your presentation layer while providing powerful tools for context management and data transformation.
In this tutorial, you will build a dynamic dashboard that demonstrates how to render templates, use standard Flask globals, and extend the template environment with custom filters and context processors.
Prerequisites
To follow this tutorial, you need a basic Flask application structure. Ensure you have a templates folder in your project root.
from flask import Flask
app = Flask(__name__)
Step 1: Rendering Your First Template
The primary way to render a template is using the render_template function. It looks for template files in the application's template_folder (defaulting to templates/).
Create a file named templates/index.html:
<!doctype html>
<title>Dashboard</title>
<h1>Welcome, {{ username }}!</h1>
<p>Status: {{ status }}</p>
Now, use render_template in your route:
import flask
@app.route("/dashboard")
def dashboard():
return flask.render_template("index.html", username="Admin", status="Active")
render_template takes the template filename as the first argument and any number of keyword arguments as context variables for the template.
Step 2: Accessing Standard Context Variables
Flask automatically injects several standard variables into every template context. These include request, session, g, and config.
You can verify this using render_template_string, which renders a template from a string instead of a file:
@app.route("/debug")
def debug_info():
flask.g.user_id = 123
flask.session["last_action"] = "login"
return flask.render_template_string(
"""
<p>Query Param 'ref': {{ request.args.ref }}</p>
<p>Global User ID: {{ g.user_id }}</p>
<p>Debug Mode: {{ config.DEBUG }}</p>
<p>Session Action: {{ session.last_action }}</p>
""",
ref=flask.request.args.get("ref", "none")
)
As seen in src/flask/templating.py, the _default_template_ctx_processor ensures g and request are available. Note that the session proxy is handled specially to track access for session saving.
Step 3: Injecting Global Context
If you have variables that should be available in every template (like a site-wide setting or a navigation menu), use the @app.context_processor decorator.
@app.context_processor
def inject_site_info():
return {
"site_name": "Flask Dashboard",
"current_year": 2023
}
Functions decorated with context_processor must return a dictionary. The keys of this dictionary will be available as variables in all templates rendered by the application.
Step 4: Creating Custom Filters
Filters allow you to transform data within your templates. You can register them using the @app.template_filter decorator.
@app.template_filter("reverse_text")
def reverse_filter(s):
return s[::-1]
You can then use this filter in your HTML:
<p>Reversed Name: {{ username | reverse_text }}</p>
If you don't provide a name to the decorator, it defaults to the function name. You can also register filters manually using app.add_template_filter(func, name).
Step 5: Streaming Large Templates
For large templates or responses that take time to generate, you can use stream_template. This returns a generator that streams the rendered content to the client.
@app.route("/large-report")
def large_report():
def generate_data():
for i in range(1000):
yield {"id": i, "val": f"Item {i}"}
return flask.Response(
flask.stream_template("report.html", items=generate_data()),
mimetype="text/html"
)
stream_template (found in src/flask/templating.py) uses stream_with_context to ensure the request context remains active while the generator is consumed.
Summary and Next Steps
You have now built a templating system that:
- Renders external files with
render_template. - Leverages Flask's built-in context (
request,g,config). - Extends the global context with
context_processor. - Transforms data with custom
template_filter. - Handles large responses with
stream_template.
To further customize template loading, you can override create_global_jinja_loader in a Flask subclass or enable EXPLAIN_TEMPLATE_LOADING in your config to debug how DispatchingJinjaLoader finds your files across the app and blueprints.