Context Processors

Reviewed & published by Brayan K

A context processor is a function that takes the request and returns a dictionary of variables, which Django merges into every template's context so those variables are available in all templates without each view passing them.

Part of the free Django course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

In this lesson you'll see how Django merges context dictionaries, configure the context_processors list in the TEMPLATES setting, write your own custom processor, and understand the built-in request, auth, and messages processors.

What You'll Learn in This Lesson

1 How Django Merges the Template Context

When you render a template, Django doesn't just use the dictionary your view returns. It builds the final context by merging several dictionaries: one dictionary from each context processor listed in your settings, plus the dictionary your view passes. The processors run first, and then the view's values are layered on top, so a key from your view wins over the same key from a processor.

This is the mental model. In your settings, the processors are listed in TEMPLATES:

# settings.py
TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
        },
    },
]
# Django builds the final template context by MERGING dicts:
#   1. one dict from EACH context processor in TEMPLATES
#   2. the dict you return from your view (merged LAST, so it wins)
# This simulation reproduces that merge in plain Python.

def request_processor(request):
    # mirrors django.template.context_processors.request
    return {"request": request}

def auth_processor(request):
    # mirrors django.contrib.auth.context_processors.auth
    return {"user": request["user"], "perms": request["user"]["perms"]}

def site_settings(request):
    # a CUSTOM processor you write yourself
    return {"site_name": "LearnCodingFast", "support_email": "help@example.com"}

CONTEXT_PROCESSORS = [request_processor, auth_processor, site_settings]

def build_context(request, view_context):
    final = {}
    for processor in CONTEXT_PROCESSORS:
        final.update(processor(request))   # processors run first
    final.update(view_context)             # the view wins on conflicts
    return final

request = {"path": "/dashboard/", "user": {"name": "Alice", "perms": ["edit"]}}
view_context = {"title": "Dashboard"}

context = build_context(request, view_context)
for key in sorted(context):
    print(f"{key} -> {context[key]}")

# EXPECTED: keys from all three processors PLUS the view's title,
# printed alphabetically (perms, request, site_name, support_email,
# title, user).

# ✅ Expected output:
# perms -> ['edit']
# request -> {'path': '/dashboard/', 'user': {'name': 'Alice', 'perms': ['edit']}}
# site_name -> LearnCodingFast
# support_email -> help@example.com
# title -> Dashboard
# user -> {'name': 'Alice', 'perms': ['edit']}

2 Writing Your Own Context Processor

A context processor is just a function that accepts the request and returns a dictionary. By convention you put these functions in a file named context_processors.py inside your app. Suppose you want the current year and a set of navigation links to be available in every template — perfect for a footer and a shared navbar.

# myapp/context_processors.py
import datetime

def common_context(request):
    return {
        "current_year": datetime.date.today().year,
        "nav_links": [
            {"label": "Home", "url": "/"},
            {"label": "Courses", "url": "/courses/"},
            {"label": "Profile", "url": request.path},
        ],
    }

Then add its dotted path to the context_processors list so Django runs it on every request:

# settings.py (inside TEMPLATES["OPTIONS"]["context_processors"])
"myapp.context_processors.common_context",

Now any template can use the values directly, with no view changes:

<!-- base.html -->
<footer>&copy; {{ current_year }} LearnCodingFast</footer>

<nav>
  {% for link in nav_links %}
    <a href="{{ link.url }}">{{ link.label }}</a>
  {% endfor %}
</nav>
# A context processor is just a function that takes the request and
# returns a dict. Here we write one that adds the current year and a
# list of nav links to EVERY template's context.

def common_context(request):
    return {
        "current_year": 2026,
        "nav_links": [
            {"label": "Home", "url": "/"},
            {"label": "Courses", "url": "/courses/"},
            {"label": "Profile", "url": request["path"]},
        ],
    }

# Simulate Django calling it during template rendering
request = {"path": "/profile/"}
extra = common_context(request)

print("Footer year:", extra["current_year"])
print("Nav links:")
for link in extra["nav_links"]:
    print(f"  {link['label']} -> {link['url']}")

# EXPECTED:
# Footer year: 2026
# Nav links:
#   Home -> /
#   Courses -> /courses/
#   Profile -> /profile/

# ✅ Expected output:
# Footer year: 2026
# Nav links:
#   Home -> /
#   Courses -> /courses/
#   Profile -> /profile/

3 The Built-in Processors: request, auth, and messages

Most of the variables you take for granted in templates come from Django's built-in processors. The auth processor is the reason every template can reference {{ user }} and {{ perms }} — the view never has to pass them. The request processor exposes {{ request }} so you can read the current path or query parameters. The messages processor exposes flash messages.

<!-- Thanks to the auth processor, these work in ANY template -->
{% if user.is_authenticated %}
  <p>Welcome back, {{ user.username }}!</p>
{% endif %}

{% if perms.blog.add_post %}
  <a href="/blog/new/">New Post</a>
{% endif %}

<!-- Thanks to the request processor -->
<p>You are on: {{ request.path }}</p>
# Built-in processors expose request, user, and perms to every template.
# This simulates how a template can reference {{ user }} and
# {% if perms.blog.add_post %} without the view passing them.

def auth_context(request):
    user = request["user"]
    return {
        "user": user,
        "perms": {p: True for p in user["perms"]},
    }

request = {
    "path": "/blog/new/",
    "user": {"name": "Bob", "is_authenticated": True, "perms": ["blog.add_post"]},
}

ctx = auth_context(request)

# Templates can now do {% if user.is_authenticated %} ...
if ctx["user"]["is_authenticated"]:
    print(f"Welcome back, {ctx['user']['name']}!")

# ... and {% if perms.blog.add_post %} ...
if ctx["perms"].get("blog.add_post"):
    print("Show the 'New Post' button")
else:
    print("Hide the 'New Post' button")

# EXPECTED:
# Welcome back, Bob!
# Show the 'New Post' button

# ✅ Expected output:
# Welcome back, Bob!
# Show the 'New Post' button

🎯 Your Turn: Return a Dictionary

A context processor must return a dictionary. Replace the blank so feature_flags makes show_beta available to every template.

# 🎯 YOUR TURN: a context processor MUST return a dict.
# Replace ___ so the function returns a dict with key "show_beta" set to True.

def feature_flags(request):
    return ___

result = feature_flags({"path": "/"})
print(result)

# EXPECTED OUTPUT:
# {'show_beta': True}

Common Errors (And How to Fix Them)

❌ My processor variable isn't showing up in the template

You wrote the function but never added it to the settings list, so Django never calls it.

✅ Fix: add its dotted path (e.g. "myapp.context_processors.common_context") to context_processors in TEMPLATES["OPTIONS"].

❌ TypeError: context processor didn't return a dictionary

Your function forgot the return, or returned None, so Django can't merge it.

✅ Fix: always end the processor with return {...} — return an empty dict {} if there's nothing to add.

❌ {{ user }} or {{ request }} is blank everywhere

A built-in processor was removed from the list, so its variables are no longer injected.

✅ Fix: make sure the request, auth, and messages processors are still listed in context_processors.

📋 Quick Reference

ItemWhat It Does
def proc(request):Signature of a context processor — takes the request
return {"key": value}Must return a dict that is merged into every context
context_processorsThe list in TEMPLATES["OPTIONS"] where you register it
...authBuilt-in: exposes user and perms
...requestBuilt-in: exposes the request object

🎯 Mini Challenge: Register and Merge Two Processors

Build two processors — one for theme/site name, one for a cart count — merge them, and confirm a per-view value overrides a processor default.

# 🎯 Mini Challenge: register multiple processors and merge them,
# then confirm a per-view value overrides a processor default.

def theme_processor(request):
    return {"theme": "light", "site_name": "LearnCodingFast"}

def cart_processor(request):
    return {"cart_count": len(request["cart"])}

PROCESSORS = [theme_processor, cart_processor]

def render_context(request, view_context):
    merged = {}
    for proc in PROCESSORS:
        merged.update(proc(request))
    merged.update(view_context)   # view overrides processor defaults
    return merged

request = {"path": "/shop/", "cart": ["pen", "ruler", "glue"]}
# The view overrides the processor's default theme:
view_context = {"theme": "dark", "title": "Shop"}

ctx = render_context(request, view_context)
print("theme:", ctx["theme"])           # dark (view wins)
print("site_name:", ctx["site_name"])   # from processor
print("cart_count:", ctx["cart_count"]) # computed by processor
print("title:", ctx["title"])           # from view

# EXPECTED:
# theme: dark
# site_name: LearnCodingFast
# cart_count: 3
# title: Shop

❓ Frequently Asked Questions

Lesson complete — your variables reach every template!

You learned how Django merges context dictionaries on each request, where to register processors in the TEMPLATES context_processors list, how to write a custom processor that returns a dict, and what the built-in request, auth, and messages processors give you for free.

Practice quiz

What does a Django context processor return?

  • A rendered HTML string
  • A dictionary merged into every template context
  • An HttpResponse object
  • A list of templates

Answer: A dictionary merged into every template context. A context processor takes the request and returns a dict that Django merges into the context of every rendered template.

Where do you register a custom context processor?

  • In urls.py
  • In the MIDDLEWARE setting
  • In the context_processors list inside TEMPLATES['OPTIONS']
  • In the INSTALLED_APPS list

Answer: In the context_processors list inside TEMPLATES['OPTIONS']. You add the function's dotted path to the context_processors list in the TEMPLATES setting's OPTIONS.

Which built-in processor exposes {{ user }} and {{ perms }} to templates?

  • django.contrib.auth.context_processors.auth
  • django.template.context_processors.request
  • django.contrib.messages.context_processors.messages
  • django.template.context_processors.static

Answer: django.contrib.auth.context_processors.auth. The auth context processor makes user and perms available in every template.

What argument does a context processor function receive?

  • The template name
  • The response
  • The view function
  • The request

Answer: The request. A context processor is a function that accepts the current request object.

Which built-in processor exposes {{ request }} in templates?

  • django.template.context_processors.request
  • django.contrib.auth.context_processors.auth
  • django.template.context_processors.csrf
  • django.contrib.messages.context_processors.messages

Answer: django.template.context_processors.request. The request context processor adds the request object to the template context.

If a context processor and a view both set the same key, which wins?

  • The processor value
  • Neither — it raises an error
  • The view value, merged last
  • The first one alphabetically

Answer: The view value, merged last. Django merges processor dicts first, then the view's context, so the view value overrides the processor's.

What happens if a context processor returns None instead of a dict?

  • Django ignores it silently
  • Django raises a TypeError trying to merge it
  • The page renders blank
  • It returns an empty dict automatically

Answer: Django raises a TypeError trying to merge it. Django expects a dict to merge; returning None raises a TypeError.

Which built-in processor exposes flash {{ messages }}?

  • django.template.context_processors.static
  • django.contrib.messages.context_processors.messages
  • django.template.context_processors.request
  • django.contrib.auth.context_processors.auth

Answer: django.contrib.messages.context_processors.messages. The messages context processor exposes the messages framework's flash messages.

When should you prefer a context processor over passing a variable in the view?

  • When the value is needed across many or all templates
  • When the value is specific to one page
  • When the value requires a heavy database query
  • Never — views are always better

Answer: When the value is needed across many or all templates. Use a processor for site-wide values; pass page-specific values directly from the view.

Why should context processors stay fast?

  • Templates cache them once
  • They only run in DEBUG mode
  • They run on every request, so a slow one slows every page
  • Django limits them to one per project

Answer: They run on every request, so a slow one slows every page. Because Django calls every processor on each request, a slow processor slows down the whole site.

Continue this course

Frequently asked questions

What is a context processor in Django?

A context processor is a small function that takes the current request and returns a dictionary of variables. Django runs every registered context processor while rendering a template and merges all of their dictionaries into the template's context. The result is that the variables they return are available in every template automatically, without each view having to pass them. Typical uses are the site name, the current year, navigation links, or the logged-in user.

Where do I register a context processor?

You register it in the TEMPLATES setting in settings.py. Inside the OPTIONS dictionary there is a context_processors list, and you add the dotted import path of your function to that list — for example 'myapp.context_processors.site_settings'. Django imports and calls every function in that list for each request, so the order in the list also controls the order in which their dictionaries are merged.

What are Django's built-in context processors?

Django ships several. The most common are django.template.context_processors.request, which exposes the request object as {{ request }}; django.contrib.auth.context_processors.auth, which exposes {{ user }} and {{ perms }}; django.contrib.messages.context_processors.messages, which exposes flash messages; and django.template.context_processors.debug and .static. A fresh project enables the request, auth, and messages processors by default.

When should I use a context processor instead of passing a variable in the view?

Use a context processor when a variable is genuinely needed across many or all templates — site-wide settings, the current user, a shopping cart count, or footer data. If a value is specific to one page, pass it from that view instead. Context processors run on every request, so keep them fast and avoid heavy database queries inside them, because a slow processor slows down every single page on the site.