The Messages Framework
Reviewed & published by Brayan K
Django's messages framework is a built-in system for showing one-time "flash" notifications — short status messages like "Profile saved!" or "Login failed" that appear once after an action and then disappear, surviving the redirect that usually follows a form submission.
Part of the free Django course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
In this lesson you'll learn how to add messages with messages.success(), messages.error(), messages.info(), and messages.warning(), how to display them in a template, and how message tags let you style each level differently.
What You'll Learn in This Lesson
- What the messages framework is and why it exists
- Adding messages with messages.success() and friends
- The five message levels and their severity
- Displaying messages by looping in a template
- Using message.tags to style each alert level
- Customising CSS classes with MESSAGE_TAGS
1 Adding Messages in a View
The messages framework lets a view attach a short notification to the current request. You import django.contrib.messages as messages and call one of its level methods, passing the request and the text to show.
The most common pattern is "do something, add a message, then redirect." Because the message is stored between the redirect and the next page, the user reliably sees it even though they land on a brand-new request.
from django.contrib import messages
from django.shortcuts import redirect, render
def update_profile(request):
if request.method == "POST":
# ... save the form ...
messages.success(request, "Your profile was updated!")
return redirect("profile")
return render(request, "profile.html")# A simplified stand-in for Django's messages framework so you can see
# the shape of the API. In a real view you'd call messages.success(request, ...).
class FakeMessages:
def __init__(self):
self.store = []
def success(self, request, text):
self.store.append(("success", text))
def error(self, request, text):
self.store.append(("error", text))
messages = FakeMessages()
fake_request = {"user": "alice"}
# Simulate handling a profile update
messages.success(fake_request, "Your profile was updated!")
messages.error(fake_request, "Avatar upload failed.")
# The template would loop over these on the next page
for level, text in messages.store:
print(f"[{level}] {text}")
# ✅ Expected output:
# [success] Your profile was updated!
# [error] Avatar upload failed.2 Displaying Messages in a Template
Once a view has added messages, your template displays them by looping over the messages variable that Django's context processor makes available. Each message has a tags attribute (like success or error) you can drop straight into a CSS class.
<!-- base.html — show flash messages near the top of every page -->
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li class="alert alert-{{ message.tags }}">
{{ message }}
</li>
{% endfor %}
</ul>
{% endif %}Here alert-{{ message.tags }} renders to something like alert-success or alert-error, letting you give each severity its own colour. Messages are consumed once read, so they appear exactly once and then vanish.
# Simulate how the template renders each message into an HTML class.
# Django gives every message a "tags" string based on its level.
messages = [
{"tags": "success", "text": "Profile updated!"},
{"tags": "warning", "text": "Your trial ends soon."},
{"tags": "error", "text": "Payment was declined."},
]
# This mirrors: <li class="alert alert-{{ message.tags }}">{{ message }}</li>
for message in messages:
css_class = f"alert alert-{message['tags']}"
print(f'<li class="{css_class}">{message["text"]}</li>')
# ✅ Expected output:
# <li class="alert alert-success">Profile updated!</li>
# <li class="alert alert-warning">Your trial ends soon.</li>
# <li class="alert alert-error">Payment was declined.</li>3 Message Levels and Tags
Django provides five severity levels, each with its own convenience method and default tag. Choosing the right one tells the user how important a notification is and lets your CSS colour it appropriately.
- messages.debug() — development-only messages, hidden in production.
- messages.info() — neutral informational notices.
- messages.success() — an action completed successfully.
- messages.warning() — something the user should be careful about.
- messages.error() — an action failed.
If your CSS framework expects different class names (for example Bootstrap uses danger instead of error), you can remap the tags in settings.py with MESSAGE_TAGS.
# settings.py — map Django's ERROR level to Bootstrap's "danger" class
from django.contrib.messages import constants as message_constants
MESSAGE_TAGS = {
message_constants.ERROR: "danger",
}# Map each Django message level to the CSS tag your framework expects.
# This mirrors the MESSAGE_TAGS setting in settings.py.
DEFAULT_TAGS = {
10: "debug",
20: "info",
25: "success",
30: "warning",
40: "error",
}
# Override ERROR (40) to use Bootstrap's "danger" instead of "error"
MESSAGE_TAGS = {40: "danger"}
def tag_for(level):
return MESSAGE_TAGS.get(level, DEFAULT_TAGS[level])
for level in (20, 25, 30, 40):
print(f"level {level} -> alert-{tag_for(level)}")
# ✅ Expected output:
# level 20 -> alert-info
# level 25 -> alert-success
# level 30 -> alert-warning
# level 40 -> alert-danger🎯 Your Turn: Pick the Right Level
Fill in the blank with the correct messages method so a successful save shows a green success notification.
# 🎯 YOUR TURN: replace ___ with the right method name (success)
class FakeMessages:
def success(self, request, text):
print(f"[success] {text}")
def error(self, request, text):
print(f"[error] {text}")
messages = FakeMessages()
request = {}
# The order was saved correctly — show a success message
messages.___(request, "Your order was placed!")
# EXPECTED OUTPUT:
# [success] Your order was placed!Why Your Messages Don't Show Up
By far the most common problem with this framework is adding a message and seeing nothing. Almost always it is one of three settings, because the framework needs all three working together — and each failure looks different.
Missing middleware is the loud one. Django raises a MessageFailure the moment you call messages.success():
A missing context processor is the quiet one — no error at all, the view runs fine, and {% for message in messages %} simply loops over nothing, because the template never received the variable.
All three pieces, in settings.py:
startproject gives you all three, which is why this usually bites on a hand-rolled settings file. If you genuinely need to add messages somewhere they may not be configured — a management command, say — pass fail_silently=True and the call becomes a no-op instead of raising.
Levels, extra_tags and Where Messages Are Stored
Each level is a number, and the ordering is worth knowing because MESSAGE_LEVEL filters on it — anything below the threshold is silently dropped. Note that success is 25, between info and warning, not above them:
That is why MESSAGE_LEVEL = 30 hides your success messages along with debug and info — a surprise if you assumed success outranked a warning.
For styling one specific message differently, extra_tags appends to the tag string without changing the level:
Storage defaults to django.contrib.messages.storage.fallback.FallbackStorage, which puts messages in a cookie and spills over into the session when they will not fit — cookies cap at about 4KB. Set MESSAGE_STORAGE to the session backend if you would rather never put message text in a cookie. Either way messages are read-once: iterating them in a template marks them consumed, so they do not reappear on the next page.
Common Errors (And How to Fix Them)
❌ Messages added but nothing shows on the page
Your template never loops over the messages variable.
✅ Fix: add a {% for message in messages %} loop to your base template.
❌ MessageFailure: You cannot add messages without...
The message middleware or context processor is missing.
✅ Fix: ensure django.contrib.messages is in INSTALLED_APPS and its middleware is enabled.
❌ Message appears twice or never disappears
You rendered the messages list more than once on the page.
✅ Fix: loop over messages in exactly one place, usually the base template.
📋 Quick Reference
| Call | What It Does |
|---|---|
| messages.success(request, text) | Add a success-level message |
| messages.error(request, text) | Add an error-level message |
| messages.info(request, text) | Add a neutral informational message |
| message.tags | CSS-friendly tag for the message level |
🏆 Mini Challenge: Checkout Notifier
Write a function that adds the right message for each checkout outcome: success when paid, warning when low stock, and error when the card is declined.
# 🏆 Mini Challenge: choose the correct message level per outcome.
class FakeMessages:
def __init__(self):
self.log = []
def success(self, req, t): self.log.append(("success", t))
def warning(self, req, t): self.log.append(("warning", t))
def error(self, req, t): self.log.append(("error", t))
messages = FakeMessages()
request = {}
def notify_checkout(outcome):
if outcome == "paid":
messages.success(request, "Payment received — thank you!")
elif outcome == "low_stock":
messages.warning(request, "Only 1 item left in stock.")
elif outcome == "declined":
messages.error(request, "Your card was declined.")
for outcome in ("paid", "low_stock", "declined"):
notify_checkout(outcome)
for level, text in messages.log:
print(f"[{level}] {text}")
# EXPECTED: one success, one warning, one error line.❓ Frequently Asked Questions
Lesson complete — you can flash messages to your users!
You now know how to add messages with the level methods, display them in a template by looping over messages, and style each level using its tag or a custom MESSAGE_TAGS mapping.
Practice quiz
What is the messages framework mainly used for?
- Storing user passwords
- One-time flash notifications shown on the next page
- Sending email
- Caching query results
Answer: One-time flash notifications shown on the next page. It attaches one-time notifications that survive a redirect.
How many built-in message levels does Django provide?
- Three
- Four
- Five
- Six
Answer: Five. debug, info, success, warning, and error are the five levels.
Which call adds a success-level message?
- messages.ok(request, text)
- messages.success(request, text)
- messages.add('success', text)
- messages.green(request, text)
Answer: messages.success(request, text). messages.success(request, text) adds a success message.
What attribute on a message gives a CSS-friendly tag like 'success'?
- message.level
- message.css
- message.tags
- message.style
Answer: message.tags. message.tags yields a tag string derived from the level.
How are messages shown in a template?
- By calling messages.render()
- By looping over the messages variable
- Automatically in every response
- With a context processor only
Answer: By looping over the messages variable. You loop over the messages variable, e.g. {% for message in messages %}.
What happens to a message once it has been read/rendered?
- It is consumed and disappears
- It persists forever
- It is emailed to the user
- It is saved to the database permanently
Answer: It is consumed and disappears. Messages are consumed once read, so they appear exactly once.
Which setting remaps message levels to custom CSS classes?
- MESSAGE_LEVELS
- MESSAGE_CLASSES
- TAGS
- MESSAGE_TAGS
Answer: MESSAGE_TAGS. MESSAGE_TAGS in settings.py remaps levels to your own class names.
Why is the common pattern 'add a message, then redirect'?
- Redirects are faster
- The message survives the redirect via Post/Redirect/Get
- Messages require a redirect to be created
- It clears the database
Answer: The message survives the redirect via Post/Redirect/Get. The message is stored across the redirect so the user reliably sees it.
Which level signals that an action failed?
- messages.info()
- messages.warning()
- messages.error()
- messages.success()
Answer: messages.error(). messages.error() indicates an action failed.
If messages are added but nothing shows, what is the most common cause?
- The template never loops over the messages variable
- The database is offline
- The user is logged out
- Django is too old
Answer: The template never loops over the messages variable. Usually the template is missing the loop over messages.
Continue this course
- Previous: URL Namespacing
- Next: User Authentication — Log users in and out and protect pages with Django's auth system
- Quick reference: Django cheat sheet
Frequently asked questions
What is Django's messages framework used for?
The messages framework lets you attach a one-time notification to the current request and display it on the next page the user sees. It is perfect for flash messages like 'Your profile was saved' or 'Login failed' that should appear once after an action and then disappear. Django stores the message between the redirect and the next request, so the user reliably sees it even after a POST-then-redirect.
What are the built-in message levels in Django?
Django ships with five convenience methods: messages.debug(), messages.info(), messages.success(), messages.warning(), and messages.error(). Each corresponds to a severity level and adds a matching CSS tag (such as 'success' or 'error') so you can style the notification differently. success and error are the two you will use most often after form submissions.
Why do my messages not appear in the template?
The most common reason is that you forgot to loop over the messages in your template, or you are not using a RequestContext. Make sure 'django.contrib.messages' is in INSTALLED_APPS, the message middleware is enabled, and your template iterates over the messages variable that the context processor provides. Messages are consumed once read, so they vanish after the first render.
How do I change the CSS classes Django uses for messages?
Each message has a tags attribute derived from its level, like 'success' or 'error'. You can map Django's default levels to your own CSS framework classes (for example Bootstrap's 'alert-danger') by setting MESSAGE_TAGS in settings.py. Then in your template you combine the framework class with message.tags to style each alert.