Flash Messages: Give users instant operational feedback

📂 Stage: Stage 2 - Interaction and Data (Core) 🔗 Related chapters: Flask-WTF 插件 · 路由(Routing)艺术


1. What is Flash?

After filling out the form and clicking submit, the page jumps directly. Was the operation successful? I tried to use an ordinary variable to save a prompt, but the result disappeared out of thin air as soon as I refreshed it. This scenario requires a mechanism that provides cross-request, one-time, and immediate feedback.

Flash message is customized for this type of demand: it is displayed once on the next requested page and then automatically cleared. It is especially suitable for "disappear after notification" functions such as login feedback, operation result prompts, and permission guidance.

1.1 Flash vs normal message comparison

TypeStorage locationRefresh performanceApplicable scenarios
Ordinary template variablesMemory (single rendering)Disappears directly after refreshingSimple prompts within the same page (such as no results in search)
Flask FlashEncrypted SessionOnly displayed on the first rendering of the next request, and will not be repeated after refresh or jumpCross-request operation feedback (login/registration/deletion successful)

💡 One sentence summary: Flask'sflashStore the message in the Session and cooperate with redirection. A prompt will only be displayed once, which perfectly solves the problem of "lost after refreshing".


2. Basic usage

2.1 Backend minimalist example

To useflash, import the function first, then call it in the route, usually paired with a redirect - because Flash relies on the next request to fetch data from the Session.

from flask import Flask, flash, redirect, url_for, render_template, request

app = Flask(__name__)
# ⚠️ 必须配置 Secret Key!Session 需要加密存储
app.secret_key = "your_secure_secret_key_here_change_it"

# 模拟登录校验
def check_login(email, pwd):
    return email == "test@example.com" and pwd == "123456"

@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        if check_login(request.form["email"], request.form["password"]):
            # 带分类的 Flash,方便前端匹配样式
            flash("登录成功!欢迎回来~", "success")
            return redirect(url_for("index"))
        else:
            flash("邮箱或密码错误,请重试", "danger")
            return redirect(url_for("login"))
    return render_template("login.html")

@app.route("/")
def index():
    return render_template("index.html")

⚠️ Emphasis added:app.secret_keyMust be set, otherwise Flash cannot be saved in Session and the function will be completely disabled!

flash()The second parameter of iscategory, used to identify the message type. We recommend directly using Bootstrap’s semantic class names so that there is almost no need to write additional styles on the front end:

category valueBootstrap class nameApplicable scenarios
successalert-successSuccessful operation (publish/delete/login)
infoalert-infoNeutral reminder (new features/point changes)
warningalert-warningWarning guidance (please log in first/will expire soon)
dangeralert-dangerError message (verification failure/network error)
# 常用分类示例
flash("文章已成功发布!", "success")
flash("今日签到积分+10", "info")
flash("请先完善个人信息", "warning")
flash("评论提交失败:包含敏感词", "danger")

3. Display Flash in template

Best Practice: Centralize Flash rendering logic in the base template (such asbase.html), all pages that inherit it will automatically display notifications to avoid code duplication.

3.1 Basic version (fixed upper right corner, can be closed)

<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}我的Flask博客{% endblock %}</title>
    <!-- 引入 Bootstrap 5 CSS(使用官方 CDN,快速上手) -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <!-- 固定右上角的 Flash 容器,带高 z-index 保证不被遮挡 -->
    {% with messages = get_flashed_messages(with_categories=true) %}
        {% if messages %}
            <div class="position-fixed top-0 end-0 p-3" style="z-index: 1050;">
                {% for category, message in messages %}
                    <!-- 可关闭的 Bootstrap Alert,支持淡入淡出 -->
                    <div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
                        {{ message }}
                        <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="关闭"></button>
                    </div>
                {% endfor %}
            </div>
        {% endif %}
    {% endwith %}

    <!-- 页面主体内容,子模板替换 -->
    <div class="container mt-5">
        {% block content %}{% endblock %}
    </div>

    <!-- 引入 Bootstrap 5 JS(关闭按钮等交互依赖) -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

3.2 Advanced version: Add emoticons

If you want the prompt to be more intuitive, you can add a simple emoji without introducing any icon library:

<!-- 替换基础版中的消息循环部分 -->
{% for category, message in messages %}
    <div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
        <div class="d-flex align-items-center">
            <!-- 根据分类自动匹配表情 -->
            {% if category == "success" %}✅
            {% elif category == "info" %}ℹ️
            {% elif category == "warning" %}⚠️
            {% elif category == "danger" %}❌
            {% else %}📢
            {% endif %}
            <span class="ms-2">{{ message }}</span>
        </div>
        <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="关闭"></button>
    </div>
{% endfor %}

3.3 Automatically disappear (fade out after 3 seconds)

Don't want users to have to close it manually every time? A simple piece of JavaScript can make the message fade out and be removed automatically:

<!-- 放在 base.html 的 </body> 前,Bootstrap JS 之后 -->
<script>
document.addEventListener("DOMContentLoaded", function() {
    document.querySelectorAll(".alert").forEach(function(alert) {
        // 3 秒后移除 show 类,触发淡出动画
        setTimeout(() => {
            alert.classList.remove("show");
            // 动画结束后彻底删除 DOM 元素
            setTimeout(() => alert.remove(), 300);
        }, 3000);   // 可自由调整停留时间(单位:毫秒)
    });
});
</script>

4. Combination of Flash and forms

4.1 Batch prompts for Flask‑WTF verification failure

When used with the Flask‑WTF plug-in, we can convert all field errors in the form into Flash messages for easy display to users.

from flask import Blueprint, flash, redirect, url_for, render_template
from .forms import RegisterForm  # 假设已定义注册表单

auth_bp = Blueprint("auth", __name__, url_prefix="/auth")

@auth_bp.route("/register", methods=["GET", "POST"])
def register():
    form = RegisterForm()
    if form.validate_on_submit():
        # 注册成功后的跳转
        flash("注册成功!请用邮箱密码登录", "success")
        return redirect(url_for("auth.login"))
    else:
        # 遍历所有表单字段的错误,批量 flash
        for field_name, errors in form.errors.items():
            # 获取字段的中文标签(例如:用户名、邮箱)
            field_label = form[field_name].label.text
            for error in errors:
                flash(f"{field_label}{error}", "danger")
    return render_template("auth/register.html", form=form)

4.2 Send multiple flashes of different categories at the same time

One operation may require multiple types of feedback: success prompts, points changes, additional guidance... Flash fully supports sending multiple messages of different categories at the same time.

# 例如删除文章后的反馈
@blog_bp.route("/delete/<int:post_id>")
def delete_post(post_id):
    flash(f"文章《{post_id}号测试》已删除", "success")
    flash("本次操作消耗积分-5", "info")
    flash("有疑问?联系管理员", "warning")
    return redirect(url_for("blog.index"))

5. Summary and quick review

5.1 Core Points

  1. Must be setSecret Key——This is a prerequisite for Flash to work properly.
  2. Use with redirection - Flash relies on the next request to read the message in the Session.
  3. Concentrate on basic template rendering - avoid duplication of code and unified management.
  4. Do not store sensitive or important data - Flash only displays once and disappears immediately after refreshing.

5.2 Quick Code Check

rear end

from flask import flash

# 发送带分类的消息
flash("用户操作成功", "success")
# 发送无分类的普通消息(模板中需用 get_flashed_messages() 不带 with_categories)
flash("普通消息")

Template (standard writing)

{% with messages = get_flashed_messages(with_categories=true) %}
    {% if messages %}
        {% for category, message in messages %}
            <div class="alert alert-{{ category }}">{{ message }}</div>
        {% endfor %}
    {% endif %}
{% endwith %}

🔗 Extended reading