Subject

When users visit a page that does not exist, Django shows a default error page. That page is useful during development, but in a real website it is better to display a clean and friendly page that matches your design. In this tutorial, you will learn how to handle 404 errors and create custom error pages in Django.

By the end, you will know how to:

1) What is a 404 Error?

A 404 error means:

The server is working, but the page the user requested does not exist.

For example:

Example:

A 404 error is normal in web applications. What matters is how you handle it.

2) Why Create a Custom 404 Page?

The default Django 404 page is not ideal for public users because:

A custom 404 page is better because it can:

3) How Django Handles 404 Errors

Django shows a 404 page when:

  1. no URL pattern matches the requested path, or
  2. a view explicitly raises Http404.

So there are two common cases:

Case 1: URL does not exist

Example:

 

# urls.py
urlpatterns = [
    path('', views.home, name='home'),
]

If the user visits /about/ and there is no matching route, Django returns a 404.

Case 2: Object does not exist inside a valid view

Example:

from django.http import Http404
from .models import Post

def post_detail(request, post_id):
    try:
        post = Post.objects.get(id=post_id)
    except Post.DoesNotExist:
        raise Http404("Post not found")

    return render(request, 'blog/post_detail.html', {'post': post})

Here, the route exists, but the specific object does not.

4) Raising a 404 Manually

You can raise a 404 error using Http404.

Example:

 

from django.http import Http404
from django.shortcuts import render

def secret_page(request):
    raise Http404("This page does not exist")

 

If this view is visited, Django will return a 404 response.

This is useful when:

5) Using get_object_or_404

Django provides a shortcut called get_object_or_404 that is much cleaner than writing try/except yourself.

Example without shortcut

from django.http import Http404
from .models import Post

def post_detail(request, post_id):
    try:
        post = Post.objects.get(id=post_id)
    except Post.DoesNotExist:
        raise Http404("Post not found")

    return render(request, 'blog/post_detail.html', {'post': post})

Example with shortcut

from django.shortcuts import render, get_object_or_404
from .models import Post

def post_detail(request, post_id):
    post = get_object_or_404(Post, id=post_id)
    return render(request, 'blog/post_detail.html', {'post': post})

This is the preferred way in most Django projects.

You can also use it with slugs:

def post_detail(request, slug):
    post = get_object_or_404(Post, slug=slug)
    return render(request, 'blog/post_detail.html', {'post': post})

6) Creating a Custom 404 Page

To create a custom 404 page, make a template named:

404.html

Django will automatically use this file when DEBUG = False.

Example template

Create this file inside your templates folder:

<!-- templates/404.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Page Not Found</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background: #f8f9fa;
            text-align: center;
            padding: 60px;
        }
        .box {
            max-width: 600px;
            margin: auto;
            background: white;
            padding: 40px;
            border-radius: 12px;
            box-shadow: 0 4px 14px rgba(0,0,0,0.08);
        }
        h1 {
            font-size: 48px;
            color: #dc3545;
        }
        p {
            font-size: 18px;
            color: #555;
        }
        a {
            display: inline-block;
            margin-top: 20px;
            text-decoration: none;
            background: #0d6efd;
            color: white;
            padding: 12px 20px;
            border-radius: 8px;
        }
        a:hover {
            background: #0b5ed7;
        }
    </style>
</head>
<body>
    <div class="box">
        <h1>404</h1>
        <p>Sorry, the page you are looking for does not exist.</p>
        <a href="/">Go Back Home</a>
    </div>
</body>
</html>

This works, but in a real project it is better to extend your base template.

7) Using Template Inheritance for 404 Page

If your project already has a base.html, your 404.html can reuse the site layout.

Example:

{% extends 'base.html' %}

{% block title %}Page Not Found{% endblock %}

{% block content %}
<div class="container text-center py-5">
    <h1 class="display-1 text-danger">404</h1>
    <h2>Page Not Found</h2>
    <p class="lead">Sorry, the page you requested does not exist or has been moved.</p>
    <a href="{% url 'home' %}" class="btn btn-primary">Return to Home</a>
</div>
{% endblock %}

This version looks more professional because it follows the same design as the rest of the site.

8) Important Setting: DEBUG = False

Your custom 404.html only appears when:

 

DEBUG = False

 

in settings.py.

Why?
Because when DEBUG = True, Django shows its default debug page to help developers troubleshoot problems.

So during development, you may not immediately see your custom 404 page.

9) Make Sure Templates Are Configured Correctly

Your template must be inside a directory Django knows.

Example settings.py:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

If your 404.html is in:

templates/404.html

Django can find it.

10) Testing Your Custom 404 Page

Since the custom 404 page is shown with DEBUG = False, testing can confuse beginners.

Method 1: Temporary production-style test

Set:

DEBUG = False
ALLOWED_HOSTS = ['127.0.0.1', 'localhost']

Then run the server and visit a URL that does not exist, such as:

 

http://127.0.0.1:8000/this-page-does-not-exist/

 

Django should display your custom 404.html.

Method 2: Use a custom test view

You can temporarily create a view that raises Http404.

from django.http import Http404

def test_404(request):
    raise Http404("Test 404 error")

and add a route:

path('test-404/', views.test_404),

Then visit /test-404/.

11) Common Beginner Mistakes

Mistake 1: Expecting custom 404 page while DEBUG = True

If DEBUG = True, Django will usually show the technical debug page instead.

Mistake 2: Wrong template location

If 404.html is not inside a recognized templates directory, Django will not find it.

Mistake 3: Wrong filename

The file must be named exactly:

404.html

not:

Mistake 4: Forgetting ALLOWED_HOSTS

When DEBUG = False, Django requires ALLOWED_HOSTS to be configured.

Example:

ALLOWED_HOSTS = ['127.0.0.1', 'localhost']

12) Customizing Other Error Pages

Django also supports other custom error pages:

You can create all of them in your templates folder.

Example:

templates/
    400.html
    403.html
    404.html
    500.html

This helps make your whole site look consistent even when errors happen.

13) Creating a Custom 500 Error Page

A 500 error means there is an internal server error.

You can create:

<!-- templates/500.html -->
{% extends 'base.html' %}

{% block title %}Server Error{% endblock %}

{% block content %}
<div class="container text-center py-5">
    <h1 class="display-1 text-warning">500</h1>
    <h2>Something went wrong</h2>
    <p class="lead">Sorry, an unexpected error occurred. Please try again later.</p>
    <a href="{% url 'home' %}" class="btn btn-primary">Return to Home</a>
</div>
{% endblock %}

This is useful because users see a polite message instead of a broken-looking page.

14) Using a Custom 404 View in urls.py

In most simple cases, just creating 404.html is enough. But Django also allows custom error handlers.

In your main urls.py, you can define:

handler404 = 'myproject.views.custom_404'

Then in views.py:

from django.shortcuts import render

def custom_404(request, exception):
    return render(request, '404.html', status=404)

This gives you more control. For example, you can:

Example with context:

def custom_404(request, exception):
    return render(request, '404.html', {
        'requested_path': request.path,
    }, status=404)

Then in 404.html:

<p>The page <strong>{{ requested_path }}</strong> could not be found.</p>

15) Example Project Structure

A simple project may look like this:

myproject/
│
├── manage.py
├── myproject/
│   ├── settings.py
│   ├── urls.py
│   └── views.py
│
├── templates/
│   ├── base.html
│   ├── 404.html
│   └── 500.html

16) Practical Example: Blog Detail Page with 404

Imagine you have a blog with posts.

Model

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=200)
    slug = models.SlugField(unique=True)
    content = models.TextField()

    def __str__(self):
        return self.title

View

from django.shortcuts import render, get_object_or_404
from .models import Post

def post_detail(request, slug):
    post = get_object_or_404(Post, slug=slug)
    return render(request, 'blog/post_detail.html', {'post': post})

URL

from django.urls import path
from . import views

urlpatterns = [
    path('post/<slug:slug>/', views.post_detail, name='post_detail'),
]

If the slug does not exist, Django automatically shows the 404 page.

Example:

This is a very common real-life use case.

17) Improving the User Experience of a 404 Page

A good 404 page should help the user continue browsing.

You can include:

Example:

{% extends 'base.html' %}

{% block title %}Page Not Found{% endblock %}

{% block content %}
<div class="container text-center py-5">
    <h1 class="display-1 text-danger">404</h1>
    <h2>Oops! This page does not exist.</h2>
    <p class="lead">You may have typed the wrong address or the page may have been removed.</p>

    <a href="{% url 'home' %}" class="btn btn-primary me-2">Go Home</a>
    <a href="{% url 'blog_list' %}" class="btn btn-outline-secondary">Read Blog Posts</a>
</div>
{% endblock %}

This is much more useful than only saying “Not found.”

18) When to Use Http404 vs Redirect

Sometimes beginners wonder whether they should show a 404 or redirect the user.

Use 404 when:

Use redirect when:

Example redirect:

from django.shortcuts import redirect

def old_page(request):
    return redirect('home')

So:

19) Full Example with Custom Handler

views.py

from django.shortcuts import render, get_object_or_404
from django.http import Http404
from .models import Post

def home(request):
    return render(request, 'home.html')

def post_detail(request, slug):
    post = get_object_or_404(Post, slug=slug)
    return render(request, 'post_detail.html', {'post': post})

def custom_404(request, exception):
    return render(request, '404.html', status=404)

urls.py

from django.contrib import admin
from django.urls import path
from . import views

handler404 = 'myproject.views.custom_404'

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', views.home, name='home'),
    path('post/<slug:slug>/', views.post_detail, name='post_detail'),
]

templates/404.html

{% extends 'base.html' %}

{% block title %}404 - Page Not Found{% endblock %}

{% block content %}
<div style="text-align:center; padding:60px;">
    <h1>404</h1>
    <p>Sorry, the requested page could not be found.</p>
    <a href="{% url 'home' %}">Back to Home</a>
</div>
{% endblock %}

20) Summary

In Django, handling 404 errors correctly is an important part of building a professional website. A 404 error happens when a page or object cannot be found. Instead of showing users a technical default page, you should create a custom 404.html page that is clean, friendly, and helpful.

You learned that:

A good error page is not just for errors. It is part of the overall quality of your website.

Mini Practice Exercise

Create a small Django project and do the following:

  1. Create a homepage.
  2. Create a blog detail page using a slug.
  3. Use get_object_or_404 in the detail view.
  4. Create a custom 404.html.
  5. Test it by visiting a non-existing URL.