Skip to content

Django Middleware

Django middleware is a hook mechanism in the request/response processing pipeline. It can intercept and process inputs and outputs globally across all requests. This article explains how middleware works, the execution order of its five hook methods (process_request, process_response, etc.), and how to write a custom middleware for tasks such as authentication checks and logging.

What Is Middleware?

Middleware is a framework-level plugin system that sits between the web server and Django’s view layer. Each middleware component handles one specific concern. Because middleware affects all requests globally, it must be used carefully — poorly written middleware can significantly degrade performance.

The MIDDLEWARE setting in settings.py is an ordered list of strings, where each string is the dotted path to a middleware class:

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',
]

The order matters: middleware is applied top-to-bottom on the way in and bottom-to-top on the way out.

Five Hook Methods

Django exposes five customizable hook methods on each middleware class:

MethodWhen it runsExecution order
process_request(self, request)Before URL routingTop → Bottom (MIDDLEWARE list)
process_view(self, request, view_func, view_args, view_kwargs)After routing, before the viewTop → Bottom
process_response(self, request, response)After the view returns a responseBottom → Top
process_template_response(self, request, response)Only when response has a render attributeBottom → Top
process_exception(self, request, exception)Only when a view raises an exceptionBottom → Top

process_request

  • Runs for every incoming request, traversing the middleware list top to bottom.
  • If a middleware does not define this method, it is skipped.
  • If this method returns an HttpResponse object, the request stops immediately and goes straight back (useful for blocking unauthorized access).
  • If it returns None, the request continues to the next middleware.

Typical use case: global authentication, IP blacklisting, rate limiting.

process_response

  • Runs for every outgoing response, traversing the middleware list bottom to top.
  • Must return an HttpResponse object — either the original response parameter or a replacement.
  • response is the HttpResponse object returned by the view. If you return a different object, the browser receives your object instead of the view’s response.

process_view

  • Runs after URL routing succeeds but before the view function is called.
  • Executed top to bottom in the MIDDLEWARE list.
  • Parameters: view_func (the matched view), view_args, view_kwargs (URL arguments).

process_template_response

  • Runs only when the response object has a render() method (i.e., a TemplateResponse).
  • Executed bottom to top.

process_exception

  • Runs only when the view raises an unhandled exception.
  • Executed bottom to top.
  • Can be used to log errors or return a custom error page.

Writing a Custom Middleware

Follow these four steps:

1. Create a folder (any name) inside your project or app directory, e.g. middlewares/.

2. Create a Python file inside it, e.g. my_middleware.py.

3. Write a class that inherits from MiddlewareMixin and define the hook methods you need (you don’t have to define all five):

from django.utils.deprecation import MiddlewareMixin
from django.http import HttpResponse

class MyMiddleware(MiddlewareMixin):

    def process_request(self, request):
        print('MyMiddleware: process_request')
        # Return None to let the request continue
        # Return an HttpResponse to block the request here
        # Example: block unauthenticated users
        # if not request.user.is_authenticated:
        #     return HttpResponse('Login required', status=403)

    def process_response(self, request, response):
        print('MyMiddleware: process_response')
        # Must return an HttpResponse object
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        print(f'MyMiddleware: process_view — calling {view_func.__name__}')

    def process_exception(self, request, exception):
        print(f'MyMiddleware: process_exception — {exception}')
        # Return None to let Django's default exception handling proceed
        # Return an HttpResponse to replace the default error page

class AnotherMiddleware(MiddlewareMixin):

    def process_request(self, request):
        print('AnotherMiddleware: process_request')

    def process_response(self, request, response):
        print('AnotherMiddleware: process_response')
        return response

4. Register the middleware in settings.py by adding its dotted path to MIDDLEWARE:

MIDDLEWARE = [
    # ... built-in middleware ...
    'myapp.middlewares.my_middleware.MyMiddleware',
    'myapp.middlewares.my_middleware.AnotherMiddleware',
]

Execution Order Visualized

Assuming two custom middleware classes MW1 (registered first) and MW2 (registered second):

Request:
  MW1.process_request  →  MW2.process_request
  MW1.process_view     →  MW2.process_view
                              view function
  MW2.process_response →  MW1.process_response
Response to client

If MW1.process_request returns an HttpResponse:

Request:
  MW1.process_request  →  (returns HttpResponse, stops here)
  MW2.process_response →  MW1.process_response
Response to client (short-circuited, view was never called)
Middleware affects every single request. Keep middleware logic lightweight and fast. Avoid database queries or blocking I/O unless absolutely necessary — these add latency to every request your application handles.
Last updated on