Khi người dùng submit form rồi bạn redirect sang trang khác, thông báo “Đã lưu thành công” sẽ biến mất nếu chỉ truyền qua biến template — vì redirect tạo một request hoàn toàn mới. Flask giải quyết việc này bằng flash(): thông báo được cất tạm vào session, sống sót qua redirect và tự xóa sau khi hiển thị một lần. Chỉ cần đặt SECRET_KEY và gọi get_flashed_messages() trong template là xong.

Vấn đề

Giả sử có form đổi mật khẩu. Cách nhiều người mới viết là xử lý xong rồi render lại ngay template cũ, kèm biến thông báo:

@app.route("/doi-mat-khau", methods=["GET", "POST"])
def doi_mat_khau():
if request.method == "POST":
# ... lưu mật khẩu mới ...
return render_template(
"doi-mat-khau.html",
thong_bao="Đổi mật khẩu thành công!",
)
return render_template("doi-mat-khau.html")

Cách này có hai vấn đề. Thứ nhất, sau khi submit thành công, người dùng ở lại đúng URL của form — bấm F5 là trình duyệt hỏi “xác nhận gửi lại biểu mẫu” và có thể gửi lại thao tác đổi mật khẩu lần nữa. Thứ hai, nếu bạn sửa bằng cách redirect về trang chủ thì biến thông báo mất hẳn, vì redirect tạo request mới và biến template không đi theo.

Giải pháp

Mẫu đúng là POST-Redirect-GET kết hợp với flash(): xử lý xong thì flash thông báo rồi redirect, trang đích tự đọc và hiển thị:

from flask import Flask, flash, redirect, render_template, url_for
app = Flask(__name__)
app.secret_key = "doi-key-nay-bang-bien-moi-truong"
@app.route("/doi-mat-khau", methods=["GET", "POST"])
def doi_mat_khau():
if request.method == "POST":
# ... lưu mật khẩu mới ...
flash("Đổi mật khẩu thành công!", "success")
return redirect(url_for("trang_chu"))
return render_template("doi-mat-khau.html")
@app.route("/")
def trang_chu():
return render_template("trang-chu.html")

Còn phía template, đặt khối đọc thông báo trong template gốc base.html để mọi trang đều hiển thị:

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

flash() cất thông báo vào session nên nó sống sót qua redirect, còn get_flashed_messages() trong template đọc xong là xóa — thông báo chỉ hiện đúng một lần. Lưu ý flash() cần SECRET_KEY để ký session cookie, thiếu key sẽ lỗi RuntimeError ngay khi gọi. Tham số category ("success", "error", "warning", "info") để CSS tô màu khác nhau cho từng loại thông báo; khi đọc phải truyền with_categories=true, nếu không category sẽ bị bỏ qua.

Áp dụng vào dự án của bạn

Dùng flash() cho mọi thao tác sau POST: đăng nhập sai, tạo/cập nhật/xóa bản ghi, upload file thành công hay thất bại. Đặt khối get_flashed_messages() trong template gốc base.html để mọi trang đều hiển thị thông báo mà không cần lặp code. Một lưu ý an toàn: đừng flash dữ liệu nhạy cảm như mật khẩu hay token — nội dung session cookie của Flask chỉ được ký chứ không mã hóa, ai cũng đọc được.

Bài viết liên quan

Câu hỏi thường gặp

Không đặt SECRET_KEY thì flash() có chạy không?

Không. Flask dùng secret key để ký session cookie, và flash() lưu thông báo trong session nên thiếu key sẽ lỗi RuntimeError ngay khi gọi. Đặt key qua biến môi trường, đừng hardcode trong code.

Thông báo flash sống được bao lâu?

Đúng một lần hiển thị: get_flashed_messages() đọc xong là xóa khỏi session, F5 sau đó không còn thấy nữa. Nếu không trang nào gọi hàm đọc, thông báo ở lại session cho tới lần đọc tiếp theo.

Có flash được nhiều thông báo cùng lúc không?

Được. Gọi flash() nhiều lần, get_flashed_messages() trả về danh sách theo đúng thứ tự bạn đã flash, kể cả khi các thông báo khác category nhau.

flash() có dùng được cho API JSON không?

Không. API nên trả JSON kèm status code thay vì flash, vì flash() dựa vào session cookie của trình duyệt — client gọi API thường không giữ session như vậy.

Lỗi thì flash category nào?

Dùng "error" cho lỗi, "success" cho thành công, "warning" và "info" cho cảnh báo và thông tin. Nhớ truyền with_categories=true khi đọc, nếu không category sẽ bị bỏ qua và mọi thông báo hiển thị giống nhau.

💬 Bạn đã thử cách này trong dự án của mình chưa? Nếu gặp lỗi hoặc có cách làm gọn hơn, để lại bình luận bên dưới — mình đọc hết và sẽ bổ sung những ý hay vào bài.

Nguồn tham khảo