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:
| Method | When it runs | Execution order |
|---|---|---|
process_request(self, request) | Before URL routing | Top → Bottom (MIDDLEWARE list) |
process_view(self, request, view_func, view_args, view_kwargs) | After routing, before the view | Top → Bottom |
process_response(self, request, response) | After the view returns a response | Bottom → Top |
process_template_response(self, request, response) | Only when response has a render attribute | Bottom → Top |
process_exception(self, request, exception) | Only when a view raises an exception | Bottom → 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
HttpResponseobject, 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
HttpResponseobject — either the originalresponseparameter or a replacement. responseis theHttpResponseobject 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., aTemplateResponse). - 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 response4. 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 clientIf 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)