Nobody likes the generic browser's error page — the kind that instantly breaks immersion and reminds users they’re looking at an unfinished feature. The good news? Django makes it surprisingly easy to replace those defaults with pages that feel polished and intentional.
This post walks through two practical methods:
Template-only overrides (drop in a file and you’re done)
Fully custom error handlers (your own views, logic, and folder structure)
1. Template‑Only Overrides (the quickest improvement)
Django has built‑in views for common HTTP errors:
- 404 — Page Not Found
- 500 — Server Error
- 403 — Permission Denied
- 400 — Bad Request
These views are located at: django.views.defaults
When these views run, they automatically try to load templates named exactly:
404.html500.html403.html400.html
So if you create one of these files inside any folder Django can find, Django will render it instead of its plain default.
Example structure:
project_root/
└── templates/
└── 404.html
└── 500.html
No custom views. No URL changes. No special configuration.
You can repeat this for the other codes as well.
2. Fully Custom Error Handlers
If you want:
- your own views,
- your own folder like templates/errors/,
- custom context,
- or error‑specific logic,
…then you define custom error handlers.
Step 1: Create custom views
Let's create a folder called errors at project level. But you could put this anywhere.
# errors/views.py
from django.shortcuts import render
def error_404(request, exception):
return render(request, "errors/404.html", status=404)
def error_500(request):
return render(request, "errors/500.html", status=500)
def error_403(request, exception):
return render(request, "errors/403.html", status=403)
def error_400(request, exception):
return render(request, "errors/400.html", status=400)
These views will handle each custom error defined.
Step 2: Register the handlers in the project-level urls.py
# project/urls.py
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("core.urls")),
]
handler404 = "errors.views.error_404"
handler500 = "errors.views.error_500"
handler403 = "errors.views.error_403"
handler400 = "errors.views.error_400"
These variable names (handler404, etc.) are mandatory. Django looks for them.
Step 3: Create your templates
I have set "DIRS": [BASE_DIR / "templates"], so Django will look for a templates folder at the project level.
templates/
└── errors/
├── 400.html
├── 403.html
├── 404.html
└── 500.html
Each template can extend your site layout and include consistent styling.
For example:
{% extends "base.html" %}
{% block content %}
<h1 class="mb-3">Upps... 404 error 🫨</h1>
<div>
<h3>Sorry, what you're looking is not here</h3>
<h4>Go <a href="{% url 'home' %}">home</a></h4>
</div>
{% endblock content %}
So this is it.
Now you're ready to handle storms.
Top comments (0)