Flash Messages

When a Bookshelf user submits the add-book form and gets redirected to the book list, there's a real gap left in the experience: nothing on the book-list page tells them the submission actually worked. Did the book get added? Did something go wrong? Flash messages are Flask's built-in answer — a one-time message queued up on one request and displayed on the very next one, right after a redirect.

Flask's flash() Function

Calling flash() queues a message to be shown on the next request that renders a template. It takes the message text and an optional second argument — a category — that lets you distinguish a success message from an error or a warning, so they can be styled differently:

# app.py
from flask import flash, redirect, url_for

@app.route('/books/add', methods=['GET', 'POST'])
def add_book():
    form = AddBookForm()

    if form.validate_on_submit():
        book = Book(title=form.title.data, pages=form.pages.data, user_id=current_user_id())
        db.session.add(book)
        db.session.commit()
        flash('Book added successfully!', 'success')
        return redirect(url_for('book_list'))

    return render_template('add_book.html', form=form)

The category — 'success' here — is just a plain string you choose; 'danger' and 'warning' are common companions, chosen to line up neatly with Bootstrap's alert classes, as the template below shows.

Displaying Flashed Messages with get_flashed_messages()

get_flashed_messages(with_categories=True) retrieves every message queued since the last time it was called, paired with its category. Placing this loop in Bookshelf's base.html — right below the nav bar, above {% block content %} — means every page that extends the base template automatically gets flash-message support, with no extra work needed on any individual page:

<!-- templates/base.html -->
<body>
    <nav>...</nav>

    {% 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 %}

    {% block content %}{% endblock %}
</body>

Because the category string ('success', 'danger', 'warning') is dropped straight into class="alert alert-{{ category }}", it lines up exactly with Bootstrap's alert-success, alert-danger, and alert-warning classes — a success message renders green, an error red, a warning amber, with no extra conditional logic needed in the template at all.

Note: This is correct, real Flask/Jinja2 code — but this website's own project doesn't run a Flask server, so no message actually flashes or renders on this page.

Why Flash Messages Sit on Top of the Session

Flash messages aren't a separate feature bolted onto Flask — they're built directly on the session mechanism from the previous lesson, Sessions & Cookies. Calling flash() appends the message into a list stored in session; calling get_flashed_messages() reads that list back out — and then removes it from the session immediately. This "read once" behavior is the entire point: it's what makes a flash message a genuinely one-time notice rather than a sticky banner that reappears on every following page. Refresh the book-list page after "Book added successfully!" first appears, and it's gone — get_flashed_messages() already consumed and cleared it on the request that displayed it.

Full Worked Example: Post/Redirect/Get with a Flash Message

This ties directly into the Post/Redirect/Get pattern from Request & Response Objects — redirecting after a successful POST so a page refresh doesn't resubmit the form. Flash messages are the natural companion to that pattern, because the redirect target is exactly where the "read once" message needs to surface:

Request 1:  POST /books/add          (form submitted)
              -> book saved to the database
              -> flash('Book added successfully!', 'success')
              -> session now holds the queued message
              -> 302 redirect to /books

Request 2:  GET /books               (browser follows the redirect)
              -> get_flashed_messages() reads the message, clears it from session
              -> "Book added successfully!" renders once, in green

Request 3:  GET /books (a manual refresh)
              -> get_flashed_messages() finds nothing left to read
              -> no message appears — exactly as intended

Without the redirect in Request 1 — that is, if the view simply re-rendered the book list directly from the POST handler — refreshing the resulting page would resubmit the form itself (the classic "confirm form resubmission" browser warning). The redirect sidesteps that entirely, and the flash message rides along for exactly one request, landing precisely on the page the user actually ends up looking at.

Coming up next: With templates, forms, and now request-scoped state all covered, Flask-SQLAlchemy Basics starts the data layer — giving Bookshelf's User, Shelf, Book, and Tag models a real database to live in.