Templates and Forms
Render HTML with Jinja2 and process form submissions.
What you will learn
- Render templates
- Use template inheritance
- Handle POST forms
Flask renders HTML using Jinja2 templates. A template is an HTML file with placeholders and small bits of logic, filled with data from your view.
from flask import render_template
@app.route("/")
def index():
posts = [{"title": "Hello", "body": "First post"}, {"title": "Flask", "body": "Is small"}]
return render_template("index.html", posts=posts, user="Ada")<!-- templates/index.html -->
{% extends "base.html" %}
{% block content %}
<h1>Welcome, {{ user }}</h1>
{% for post in posts %}
<article>
<h2>{{ post.title }}</h2>
<p>{{ post.body|truncate(80) }}</p>
</article>
{% else %}
<p>No posts yet.</p>
{% endfor %}
{% endblock %}{{ ... }}prints a value, and Jinja auto-escapes it to prevent XSS.{% ... %}is a statement (if,for,extends,block).|filtertransforms values:upper,length,default,truncate.
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<title>{% block title %}My App{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<nav><a href="{{ url_for('index') }}">Home</a></nav>
{% block content %}{% endblock %}
</body>
</html>url_for builds URLs from view function names, so links do not break when you rename a route. Static files go in a static/ folder.
from flask import request, redirect, url_for, flash
app.secret_key = "change-me" # load from an environment variable in real apps
@app.route("/contact", methods=["GET", "POST"])
def contact():
if request.method == "POST":
email = request.form.get("email", "").strip()
message = request.form.get("message", "").strip()
if not email or not message:
flash("Both fields are required.")
else:
save_message(email, message)
flash("Thanks, we got your message!")
return redirect(url_for("contact")) # Post/Redirect/Get
return render_template("contact.html")<form method="post">
{% for m in get_flashed_messages() %}<p class="notice">{{ m }}</p>{% endfor %}
<label>Email <input name="email" type="email" required></label>
<label>Message <textarea name="message" required></textarea></label>
<button>Send</button>
</form>Redirecting after a successful POST stops the browser from resubmitting the form when the user refreshes. For CSRF protection and validation, add Flask-WTF.
Never build HTML by concatenating user input into strings. Let Jinja escape it, and only use |safe on content you have sanitized.
Try it yourself
Create a /greet page with a form that asks for a name and shows "Hello, <name>!" after submission, using a base template.
Show solution
@app.route("/greet", methods=["GET", "POST"])
def greet():
name = request.form.get("name") if request.method == "POST" else None
return render_template("greet.html", name=name)
# greet.html: {% if name %}<h1>Hello, {{ name }}!</h1>{% endif %}
# <form method="post"><input name="name"><button>Go</button></form>