Real pages need layouts, loops and escaping, which hand-built strings get wrong.
Flask uses Jinja for HTML. Put templates in a templates/ folder next to your app module and call render_template("page.html", **variables).
Inside a template:
{{ expression }}outputs a value;{{ user.name }}works for attributes and dict keys{% if %},{% for %}and{% endfor %}provide logic{{ value|filter }}transforms output, such as|title,|lengthor|default("none")
Autoescaping is on for .html, .htm, .xml, .xhtml and .svg templates: <script> in a user's name is rendered as text, which prevents cross-site scripting. Only mark content safe (|safe or Markup) when you generated it and trust it completely.
Layouts use template inheritance. A base.html defines the page skeleton with named blocks; child templates write {% extends "base.html" %} and fill blocks like {% block content %}.
Flask makes a few objects available in every template without passing them: request, session, g, config, url_for() and get_flashed_messages(). You can add your own with a @app.context_processor that returns a dict.
For quick experiments, render_template_string renders a template from a string, with autoescaping always on. In real apps prefer files: they are cached and editors highlight them.
Keep logic in views and presentation in templates. If a template needs complex computation, prepare the data in Python first.
Example
from flask import Flask, render_template_string
app = Flask(__name__)
# In a real app this lives in templates/users.html and you call render_template.
PAGE = """<h1>{{ title|title }}</h1>
<ul>{% for u in users %}<li>{{ u.name }}{% if u.admin %} (admin){% endif %}</li>{% endfor %}</ul>
<p>{{ users|length }} users, path {{ request.path }}</p>"""
@app.get("/users")
def users():
data = [{"name": "Ann", "admin": True}, {"name": "<script>x</script>", "admin": False}]
return render_template_string(PAGE, title="team members", users=data)
print(app.test_client().get("/users").text)
# <h1>Team Members</h1>
# <ul><li>Ann (admin)</li><li><script>x</script></li></ul>
# <p>2 users, path /users</p>Remember
- render_template("x.html", **vars) renders templates/x.html
- {{ }} outputs values, {% %} runs logic, |filters transform
- Autoescaping protects .html templates from XSS; |safe turns it off
Watch out for
- Using |safe on user-provided content, which reopens the XSS hole autoescaping closed.