Learn / Frameworks / Flask / Templates and Forms

Beginner 15 min

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).
  • |filter transforms 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.

Security

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>