~7 min read • Updated Mar 14, 2026
Introduction
Middleware in Django is a lightweight, low-level plugin system that allows developers to globally modify or process incoming requests and outgoing responses. Each middleware component performs a specific task. For example, AuthenticationMiddleware associates users with requests using sessions.
This article explains how middleware works, how to activate it, and how to write custom middleware components.
Writing Custom Middleware
A middleware component is a callable that receives a request and returns a response. Middleware can be implemented as a function or as a class.
Function-Based Middleware
def simple_middleware(get_response):
# One-time configuration and initialization.
def middleware(request):
# Code executed before the view (and later middleware).
response = get_response(request)
# Code executed after the view.
return response
return middlewareClass-Based Middleware
class SimpleMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration.
def __call__(self, request):
# Code executed before the view.
response = self.get_response(request)
# Code executed after the view.
return responseThe get_response callable may be the next middleware in the chain or a wrapper around the view. Middleware does not need to know what comes next.
Notes on __init__
- It must accept only the
get_responseargument. - It runs once when the server starts, not per request.
Marking Middleware as Unused
If __init__ raises MiddlewareNotUsed, Django removes the middleware from the processing chain.
Activating Middleware
To activate middleware, add its full Python path to the MIDDLEWARE list in Django settings:
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 of middleware is important because some middleware depend on others. For example, AuthenticationMiddleware must run after SessionMiddleware.
Middleware Order and Layering
During the request phase, middleware is applied top-down. During the response phase, middleware is applied bottom-up.
This structure behaves like an onion: each middleware layer wraps the next. If a middleware returns a response without calling get_response, inner layers and the view will not execute.
Additional Middleware Hooks
Class-based middleware can define three optional methods for more control.
process_view()
Called just before Django calls the view.
request: The request object.view_func: The view function.view_args: Positional arguments for the view.view_kwargs: Keyword arguments for the view.
If it returns None, Django continues processing. If it returns an HttpResponse, the view is skipped.
Note: Accessing request.POST here prevents later modification of upload handlers.
process_exception()
Called when the view raises an exception.
If it returns an HttpResponse, Django uses it. Otherwise, default exception handling applies.
process_template_response()
Called after the view returns a TemplateResponse or similar object.
It must return a response object with a render() method. It may modify the response or return a new one.
Handling Streaming Responses
StreamingHttpResponse does not have a content attribute. Middleware must check whether a response is streaming:
if response.streaming:
response.streaming_content = wrap_streaming_content(response.streaming_content)
else:
response.content = alter_content(response.content)Streaming content may be too large to load into memory. Middleware must wrap it, not consume it:
def wrap_streaming_content(content):
for chunk in content:
yield alter_content(chunk)Conclusion
Django’s middleware system provides powerful hooks into the request/response cycle. By understanding middleware ordering, lifecycle, and special hooks, developers can build custom middleware that enhances security, performance, and functionality across an entire Django application.
Streaming Response Handling
StreamingHttpResponse supports both synchronous and asynchronous iterators. Middleware that wraps streaming content must match the iterator type. If your middleware needs to support both, check StreamingHttpResponse.is_async to determine the correct behavior.
Exception Handling in Middleware
Django automatically converts exceptions raised by views or middleware into appropriate HTTP responses. Known exceptions become 4xx responses, while unknown exceptions become 500 errors.
This conversion happens before and after each middleware layer, acting like a thin film between layers of the “onion.” As a result:
- Middleware always receives an
HttpResponsefromget_response, never an exception. - Even if the next middleware raises
Http404, your middleware receives a response withstatus_code = 404.
To disable this conversion and allow exceptions to propagate, set DEBUG_PROPAGATE_EXCEPTIONS = True.
Asynchronous Support in Middleware
Middleware can support synchronous requests, asynchronous requests, or both. Django adapts requests to match middleware capabilities, but this may reduce performance.
Capability Flags
Set these attributes on your middleware factory or class:
sync_capable: Defaults to True.async_capable: Defaults to False.
If both are True, Django passes the request without adaptation. You can detect async mode by checking whether get_response is a coroutine using iscoroutinefunction.
Decorators for Middleware Capabilities
Django provides decorators:
sync_only_middleware()async_only_middleware()sync_and_async_middleware()
Hybrid Middleware Example
from asgiref.sync import iscoroutinefunction
from django.utils.decorators import sync_and_async_middleware
@sync_and_async_middleware
def simple_middleware(get_response):
if iscoroutinefunction(get_response):
async def middleware(request):
response = await get_response(request)
return response
else:
def middleware(request):
response = get_response(request)
return response
return middlewareNote: Even if the view is async, Django may call your middleware in sync mode if synchronous middleware exists between you and the view.
Asynchronous Class-Based Middleware
from asgiref.sync import iscoroutinefunction, markcoroutinefunction
class AsyncMiddleware:
async_capable = True
sync_capable = False
def __init__(self, get_response):
self.get_response = get_response
if iscoroutinefunction(self.get_response):
markcoroutinefunction(self)
async def __call__(self, request):
response = await self.get_response(request)
return responseUpgrading Legacy Middleware
Django provides MiddlewareMixin to help upgrade pre-Django 1.10 middleware to the modern system.
What MiddlewareMixin Provides
- An
__init__()that storesget_response. - A
__call__()method that:
Calls process_request()
Calls get_response()
Calls process_response()Under MIDDLEWARE_CLASSES, __call__() is ignored and Django calls process_request and process_response directly.
Behavioral Differences Between MIDDLEWARE and MIDDLEWARE_CLASSES
1. Response Flow
Under MIDDLEWARE, middleware behaves like an onion: only layers that saw the request will see the response.
Under MIDDLEWARE_CLASSES, all process_response methods run, even if earlier middleware short-circuited.
2. Exception Handling
Under MIDDLEWARE, process_exception only handles exceptions from the view.
Under MIDDLEWARE_CLASSES, it also handles exceptions from process_request.
3. Exceptions in process_response
Under MIDDLEWARE_CLASSES, exceptions in process_response skip earlier middleware and always return a 500 error.
Under MIDDLEWARE, exceptions are converted to HTTP responses and passed to the next middleware.
Conclusion
Django’s middleware system provides powerful hooks for customizing request and response processing. Understanding streaming behavior, exception handling, async support, and legacy compatibility enables developers to build robust, efficient, and modern middleware components tailored to their application’s needs.
Written & researched by Dr. Shahin Siami