Django middleware is a hook into Django’s request/response processing, allowing you to globally alter requests or responses, or to perform actions at specific points in the request/response lifecycle. The most surprising true thing about Django middleware is that it’s not just a series of independent functions; it’s a chain where each link can modify the request before passing it to the next, and modify the response before returning it to the client.

Let’s see it in action. Imagine you have a simple Django app.

# myapp/views.py
from django.http import HttpResponse

def hello_world(request):
    return HttpResponse("Hello, World!")

And your urls.py:

# myproject/urls.py
from django.urls import path
from myapp import views

urlpatterns = [
    path('hello/', views.hello_world),
]

Now, let’s add some middleware. In your settings.py, the MIDDLEWARE setting is a list of strings, where each string is the Python path to a middleware class.

# myproject/settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    # Our custom middleware
    'myapp.middleware.MyCustomMiddleware',
]

Here’s a simple custom middleware:

# myapp/middleware.py
class MyCustomMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        # One-time configuration and initialization.

    def __call__(self, request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.
        print(f"Before view: Request path is {request.path}")

        response = self.get_response(request)

        # Code to be executed for each request/response after
        # the view is called.
        print(f"After view: Response status code is {response.status_code}")

        return response

When a request comes in for /hello/, Django processes it through the middleware chain. The __init__ method of each middleware is called once when Django starts up, receiving the get_response callable. The __call__ method is then executed for every request.

The get_response callable is the key:

  • For the first middleware in the list, get_response points to Django’s core request handler (which eventually calls the view).
  • For subsequent middleware, get_response points to the next middleware’s __call__ method.

So, when MyCustomMiddleware.__call__ is executed:

  1. It prints "Before view: Request path is /hello/".
  2. It calls self.get_response(request). This passes the request object to the next middleware in the chain (or to the view if MyCustomMiddleware is last).
  3. The subsequent middleware (or the view) executes and returns a response object.
  4. MyCustomMiddleware receives this response object.
  5. It prints "After view: Response status code is 200".
  6. It returns the response object.

This creates a nested structure. If you had another middleware, AnotherMiddleware, placed before MyCustomMiddleware:

MIDDLEWARE = [
    # ... other middleware ...
    'myapp.middleware.AnotherMiddleware',
    'myapp.middleware.MyCustomMiddleware',
    # ... other middleware ...
]

The flow would be: AnotherMiddleware.__call__ calls MyCustomMiddleware.__call__ MyCustomMiddleware.__call__ calls get_response (which is now the next middleware or the view) The view executes and returns a response. MyCustomMiddleware processes the response. AnotherMiddleware receives the response from MyCustomMiddleware and processes it.

This nested execution is why order matters critically. Middleware that needs to run before others (e.g., authentication, session management) should be higher up in the list. Middleware that needs to run after others (e.g., modifying the response based on view logic) should be lower down.

Consider CommonMiddleware. If it’s placed after CsrfViewMiddleware, it might strip a Content-Type header that CsrfViewMiddleware needs to validate the request, leading to CSRF errors even when the token is present. Conversely, if CsrfViewMiddleware is placed after middleware that modifies the request body, it might not be able to find the CSRF token if it’s been altered.

The SecurityMiddleware is a prime example of a middleware that should almost always be at the very top. It sets various security-related HTTP headers (like X-Content-Type-Options, X-XSS-Protection) that protect your site from common attacks. If it’s placed lower, other middleware or even the view could potentially override or interfere with these headers before SecurityMiddleware has a chance to set them.

Similarly, SessionMiddleware needs to run before AuthenticationMiddleware because AuthenticationMiddleware relies on request.session being populated by SessionMiddleware to retrieve the logged-in user. If SessionMiddleware is below AuthenticationMiddleware, the user won’t be able to log in.

The django.middleware.common.CommonMiddleware is a utility that handles things like normalizing request methods (e.g., ensuring PUT and POST are treated consistently) and setting the X-XSS-Protection header (though SecurityMiddleware is now the primary handler for this). Its position is less critical than security or session middleware, but placing it too late might mean its normalization logic isn’t applied to requests that have already been modified by other middleware.

The django.middleware.csrf.CsrfViewMiddleware must run after SessionMiddleware and AuthenticationMiddleware because it needs access to the user’s session and potentially the user object to determine if CSRF protection should be applied or bypassed for certain requests. It also needs to run before the view itself to ensure the CSRF token is validated before any sensitive data is processed.

The django.contrib.auth.middleware.AuthenticationMiddleware binds the user to the request object. It must run after SessionMiddleware because it uses request.session to retrieve the user. If it runs before SessionMiddleware, request.user will always be an anonymous user.

The django.contrib.messages.middleware.MessageMiddleware depends on SessionMiddleware to store and retrieve messages. It should run after SessionMiddleware so that messages can be correctly associated with a session.

The django.middleware.clickjacking.XFrameOptionsMiddleware protects your site from clickjacking attacks by setting the X-Frame-Options header. Like SecurityMiddleware, it’s a security-focused middleware that generally belongs near the top to ensure the header is applied consistently.

The magic of middleware lies in this layered, sequential processing. Each __call__ method receives the request, performs its actions, passes the request along using get_response, and then processes the response. This creates a powerful, albeit sometimes complex, system for extending Django’s core functionality.

The next concept you’ll likely encounter is how to write middleware that only runs for specific request methods or paths, or how to write middleware that doesn’t wrap the response but only acts on the request.

Want structured learning?

Take the full Django course →