Filter concepts by levelShowing all levels.

Django · Middleware

Middleware

The request/response lifecycle, writing custom and async middleware, and the built-in stack's dependency order

  • Middleware wraps the whole chain, onion-styleMIDDLEWARE order = request order in; the REVERSE for response processing out
  • Short-circuit by returning a response directlyskips get_response() — the view and every later middleware never run
  • Per-request state goes on request, never selfa middleware instance is created once and shared across every request
  • The default stack is a dependency chainAuthenticationMiddleware and MessageMiddleware both need SessionMiddleware first

1.The request/response lifecycle, ordering, and short-circuiting

Every middleware wraps the ENTIRE rest of the chain — its code before calling get_response(request) runs on the way IN (request processing), and its code after runs on the way OUT (response processing), for every single request, in the order MIDDLEWARE lists them going in and the REVERSE order coming back out. This is why MIDDLEWARE's order matters: SessionMiddleware must run before AuthenticationMiddleware (auth needs the session already loaded), and a middleware near the top of the list is also the last to see the response on the way out. Short-circuiting means a middleware can return an HttpResponse directly instead of calling get_response() — the request never reaches any middleware or view further down the chain, and the response starts unwinding back out immediately from that point.

Key features

  • Middleware wraps the entire chain in ONE pass — code before get_response(request) runs on the way in, code after runs on the way out, for every request
  • MIDDLEWARE order is the request-processing order going IN, and the reverse order for response processing coming OUT — a middleware near the top sees the request first and the response last
  • SessionMiddleware must precede AuthenticationMiddleware in MIDDLEWARE — authentication depends on the session already being loaded, a concrete, load-bearing example of why order matters
  • A middleware short-circuits by returning an HttpResponse instead of calling get_response(request) — the view and every middleware layer closer to the center never run for that request
  • process_exception() (an older-style hook, still supported) and exception handling in a __call__-based middleware's try/except are how middleware can react to a view raising an unhandled exception, not just a normal return
  • Function-based (using MiddlewareMixin or a plain closure) and class-based __call__ middleware are both valid, current styles — a middleware is fundamentally just a callable that takes get_response and returns a callable taking request

Request vs response processing, by position in MIDDLEWARE

Request vs response processing, by position in MIDDLEWARE
PositionSees requestSees response
First in MIDDLEWAREfirst (outermost, before anything else runs)last (after every other middleware has already processed it)
Last in MIDDLEWARElast (closest to the view)first (closest to where the view returned)
The onion — request in, response out, in reverse

SecurityMiddleware

first in, last out

SessionMiddleware

must precede AuthenticationMiddleware

AuthenticationMiddleware

sets request.user

view

the center — last in, first out

  1. SecurityMiddleware — first in, last out
  2. SessionMiddleware — must precede AuthenticationMiddleware
  3. AuthenticationMiddleware — sets request.user
  4. view — the center — last in, first out

Command

python
class MyMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        # request processing
        response = self.get_response(request)
        # response processing
        return response

Middleware wraps the whole chain in one onion-layered pass — MIDDLEWARE's order is the request-processing order going in, and the REVERSE order for response processing coming out. SessionMiddleware must precede AuthenticationMiddleware. Short-circuiting (returning a response instead of calling get_response()) is a sanctioned way to reject a request before it reaches the view or any later middleware.

2.Writing custom middleware, exception handling, and async support

A custom middleware is a callable — __init__(self, get_response) stores the next layer, __call__(self, request) runs it. process_exception(self, request, exception) is an optional extra method Django calls specifically when a view raises an unhandled exception, letting middleware react (log it, return a custom error response) without needing a try/except around get_response() itself. Async middleware supports async def __call__ (or a sync one Django auto-wraps) so it can run inside an ASGI deployment without forcing a sync/async context switch for every single middleware layer — marked via async_capable = True (and sync_capable if it also supports WSGI). State management means per-request data a middleware wants to make available to the view/later middleware — set it as an attribute on request itself (e.g. request.tenant = ...), since request is the one object that survives the whole chain.

Key features

  • A middleware needs only __init__(self, get_response) and __call__(self, request) — no base class required, though MiddlewareMixin exists for compatibility with the older separate-method style
  • process_exception(self, request, exception) is called only when a view raises an unhandled exception — returning an HttpResponse from it short-circuits Django's normal exception handling for that request
  • async_capable = True (class attribute) declares a middleware supports async def __call__ — Django's adapter wraps a sync-only middleware to still work under ASGI, but at the cost of a sync/async context switch for that layer
  • A fully async-capable middleware chain avoids that per-layer switching cost, which matters specifically for high-concurrency ASGI deployments — not a correctness requirement for smaller/WSGI-only projects
  • Per-request custom data belongs as an attribute on request (e.g. request.tenant, request.request_id) — this is the documented convention, since request is the one object passed through every remaining layer and into the view
  • A middleware's __init__ runs ONCE at server startup (or worker startup), not per-request — code in __init__ that depends on per-request state, rather than one-time setup, belongs in __call__ instead

Where different middleware logic belongs

Where different middleware logic belongs
ConcernGoes in
One-time setup (config, connections)__init__(self, get_response)
Per-request logic__call__(self, request)
Reacting to an unhandled view exceptionprocess_exception(self, request, exception)
Per-request data for later middleware/the viewan attribute on request itself
Where different middleware logic belongs

__init__(self, get_response)

one-time setup — runs once at startup

__call__(self, request)

per-request logic

process_exception(...)

reacts to an unhandled view exception

request.<attr> = ...

per-request data for later middleware/the view — never self

  1. __init__(self, get_response) — one-time setup — runs once at startup
  2. __call__(self, request) — per-request logic
  3. process_exception(...) — reacts to an unhandled view exception
  4. request.<attr> = ... — per-request data for later middleware/the view — never self

Command

python
class MyMiddleware:
    async_capable = True
    sync_capable = True

    def __init__(self, get_response):
        self.get_response = get_response

    async def __call__(self, request):
        response = await self.get_response(request)
        return response

A middleware needs only __init__(get_response)/__call__(request) — no base class required. process_exception() reacts to an unhandled view exception specifically; return None to let Django's default handling continue. async_capable declares async support, avoiding a per-layer sync/async adaptation cost under ASGI. Per-request state belongs as an attribute on request, never on self (a middleware instance is shared across every request) or a module-level variable.

3.The built-in middleware stack, and why its default order matters

A new Django project's default MIDDLEWARE already orders SecurityMiddleware (HTTPS/HSTS-related headers, first, closest to raw request handling), SessionMiddleware (loads request.session), CommonMiddleware, CsrfViewMiddleware (checks CSRF tokens on unsafe methods), AuthenticationMiddleware (sets request.user, needs the session already loaded), MessageMiddleware (the messages framework, needs the session for storage), and XFrameOptionsMiddleware (clickjacking protection). Each depends on state an earlier middleware provides — AuthenticationMiddleware needs SessionMiddleware's session, MessageMiddleware's default storage needs the session too — which is why removing or reordering one is rarely safe without understanding exactly what it depends on.

Key features

  • Default order: SecurityMiddleware, SessionMiddleware, CommonMiddleware, CsrfViewMiddleware, AuthenticationMiddleware, MessageMiddleware, XFrameOptionsMiddleware (as of the current default project template)
  • AuthenticationMiddleware sets request.user — it must come after SessionMiddleware, since it reads the already-loaded session to know who's logged in
  • CsrfViewMiddleware's actual token check happens in a process_view hook (after URL resolution finds a view), not simple request/response wrapping — but it still must be positioned early enough in MIDDLEWARE to see every request
  • MessageMiddleware (the messages framework) defaults to session-backed storage — it depends on SessionMiddleware for the same reason AuthenticationMiddleware does
  • SecurityMiddleware handles SECURE_SSL_REDIRECT, SECURE_HSTS_SECONDS, and several other security-header settings — positioned first so these apply before any other processing
  • XFrameOptionsMiddleware sets the X-Frame-Options header (clickjacking protection) — a response-processing concern, so its position also determines how late it can override headers other middleware already set

Default middleware, in order, and what each depends on

Default middleware, in order, and what each depends on
MiddlewareDepends on
SecurityMiddlewarenothing — runs first by design
SessionMiddlewarenothing — but many later middleware depend on IT
CommonMiddlewarenothing load-bearing from earlier middleware
CsrfViewMiddlewareURL resolution having found a view (checked via process_view)
AuthenticationMiddlewareSessionMiddleware (reads request.session)
MessageMiddlewareSessionMiddleware (default storage is session-backed)
XFrameOptionsMiddlewarenothing load-bearing — a response-header concern

Command

python
MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    ...
    "django.contrib.auth.middleware.AuthenticationMiddleware",
]

The default MIDDLEWARE order is a real dependency chain, not arbitrary: SessionMiddleware before AuthenticationMiddleware (needs the session) and MessageMiddleware (default storage is session-backed); SecurityMiddleware first for HTTPS/HSTS concerns before anything else runs. CsrfViewMiddleware's real check happens in process_view(), after URL resolution, not plain request processing. Removing a built-in middleware means checking everything downstream that depends on it too.

Remember

  • MIDDLEWARE order is the request-processing order going in, and the reverse order for response processing coming out — a middleware first in the list is last to see the response.
  • Short-circuiting (returning a response instead of calling get_response()) is a sanctioned way to reject a request before it reaches the view or any later middleware.
  • A middleware needs only __init__(get_response)/__call__(request) — process_exception() reacts to an unhandled view exception; async_capable declares async support.
  • Per-request data belongs on request itself, never on self (shared across every request) or a module-level variable — the default middleware stack's order is a real dependency chain, not cosmetic.

Pro tip

  • CsrfViewMiddleware's real check runs in process_view(), after URL resolution finds a view — which is what lets it see a view-level @csrf_exempt marker.
  • Removing a built-in middleware (e.g. SessionMiddleware) means checking everything downstream that depends on it — AuthenticationMiddleware and MessageMiddleware both do.

Next up

  • Signals
  • Transactions + Signals
  • Static Files

Sources

  • Middleware — docs.djangoproject.com/en/5.2/topics/http/middleware/
  • Every claim checked against Django 5.2 LTS docs · checked 2026-08-24

Spotted a mistake on this page? Tell us.