Django Inclusion Tags: Reusable Template Components

Blog / Django · August 27, 2017 · Updated June 10, 2026 · 7 min read
Django Inclusion Tags: Reusable Template Components

A Django inclusion tag is a custom template tag that renders a small template fragment using a context dictionary it builds in Python. You reach for one whenever you need a reusable, data-driven UI component — a navigation menu, a breadcrumb trail, a card list, or a sidebar widget — that should look the same everywhere but pull fresh data on each render.

Inclusion tags are one flavour of Django custom template tags and filters. This guide shows how to build one in Django 5.x, pass arguments, capture the parent context with takes_context=True, and choose between inclusion_tag, simple_tag, and {% include %}.

Key takeaways

  • An inclusion tag = Python logic + a template fragment. The tag function returns a dict, and that dict becomes the context of the rendered template.
  • Tags live in a templatetags/ package inside an app; the package needs an __init__.py and the app must be in INSTALLED_APPS.
  • Register with @register.inclusion_tag('app/_widget.html'); load it in a template with {% load %} and call it like {% my_tag arg %}.
  • The fragment does not see the page's context unless you pass takes_context=True, which injects the parent context as the function's first argument.
  • Keep database queries in the tag function, not the template. Use simple_tag when you only need a string, and {% include %} when there's no Python logic at all.
  • After you create a new templatetags/ module, restart the dev server so Django picks it up.

What is a Django inclusion tag?

An inclusion tag couples two things: a Python function that gathers data and a template fragment that renders it. When you call the tag in a template, Django runs your function, takes the dictionary it returns, uses that dictionary as the context, and renders the named fragment in place.

That makes inclusion tags the natural fit for repeated UI components. Instead of copying the same {% for %} loop and markup into ten templates, you write the query once and drop {% latest_posts 5 %} wherever you need it. If you are new to how templates resolve variables and tags, the basics of the Django template engine is a good primer.

How do you create the templatetags package?

Custom tags must live inside a Django app, in a templatetags/ directory at the same level as models.py and views.py. The directory must contain an __init__.py so Python treats it as a package. The module filename (here poll_extras.py) is the name you'll later pass to {% load %} — not the app name — so pick something that won't clash with another app's library.

polls/
    |- __init__.py
    |- models.py
    |- templates/
    |   |- polls/
    |       |- _choice_list.html
    |- templatetags/
    |   |- __init__.py
    |   |- poll_extras.py
    |- urls.py
    |- views.py

Every tag module needs a module-level variable named register that is a template.Library() instance. All your tags and filters are registered on it.

# polls/templatetags/poll_extras.py
from django import template

register = template.Library()

How do you write an inclusion tag?

Decorate a function with @register.inclusion_tag(), passing the path to the fragment it should render. The function gathers data and returns a dict — every key in that dict becomes a variable in the fragment. Notice the database query lives in Python, not in the template.

# polls/templatetags/poll_extras.py
from django import template

register = template.Library()

@register.inclusion_tag('polls/_choice_list.html')
def show_choices(poll):
    # Query the DB here in Python, never inside the template.
    choices = poll.choice_set.all()
    return {'choices': choices}

Now create the fragment it renders. Prefix partial templates with an underscore (_choice_list.html) so they're easy to spot as fragments that are never served on their own.

{# polls/templates/polls/_choice_list.html #}
<ul class="choices">
  {% for choice in choices %}
    <li>{{ choice }}</li>
  {% endfor %}
</ul>

Finally, {% load %} the library at the top of the page template and call the tag. The argument you pass (poll) is handed straight to your function.

{% load poll_extras %}

<h2>{{ poll.question }}</h2>
{% show_choices poll %}

How do you pass multiple arguments to an inclusion tag?

An inclusion tag accepts the same positional and keyword arguments as any Python function, including defaults. This lets one tag adapt to several contexts:

@register.inclusion_tag('polls/_choice_list.html')
def show_choices(poll, limit=5, css_class='choices'):
    choices = poll.choice_set.all()[:limit]
    return {'choices': choices, 'css_class': css_class}

Then call it with {% show_choices poll limit=3 css_class="featured" %}.

How does takes_context=True work?

By default the fragment is rendered with only the dict your function returns — it does not automatically inherit the page's context. If the fragment needs something from the surrounding page (the logged-in user, the current request, a csrf_token), pass takes_context=True. Django then injects the parent context as the function's first argument, which by convention you name context.

@register.inclusion_tag('polls/_choice_list.html', takes_context=True)
def show_choices(context, poll):
    request = context['request']
    choices = poll.choice_set.all()
    return {
        'choices': choices,
        'user': context['user'],   # value pulled from the parent context
    }

Call it exactly as before — takes_context changes only the signature, not the template call:

{% show_choices poll %}

inclusion_tag vs simple_tag vs {% include %} vs {% with %}

Choosing the right tool keeps templates clean. Use this table to decide:

Feature inclusion_tag simple_tag {% include %} {% with %}
Returns a dict (becomes the fragment's context) a string (or value) nothing (renders a file) nothing (binds a variable)
Renders a template? Yes, the named fragment No Yes, a static file No
Runs Python logic? Yes Yes No No
Can receive the page context? Yes, with takes_context=True Yes, with takes_context=True Inherits the full page context Inherits the page context
Best for reusable data-driven UI components computing a single value/HTML snippet including a template with no extra logic aliasing an expensive variable in one template

In short: pick inclusion_tag for a widget that needs both data and markup, simple_tag when you only return a value, and {% include %} when the partial needs no new Python at all.

Best practices for inclusion tags

  • Keep DB queries out of templates. Run them in the tag function so the data layer stays testable and the template stays presentational.
  • Cache expensive tags. Wrap costly queries with Django's cache framework or template fragment caching when the same widget renders on every page.
  • Underscore-prefix fragments. Naming partials _widget.html signals they're never served directly.
  • Register the app in INSTALLED_APPS. {% load %} only finds libraries in installed apps.
  • Restart the dev server after adding a new templatetags/ module — Django loads tag libraries at startup, so a new module won't be discovered until you restart.
  • Reuse, don't repeat. If you find the same loop-and-markup in several templates, that's the signal to extract an inclusion tag.

For the wider picture — filters, simple tags, and advanced node parsing — see our guide to Django custom template tags and filters and the Django template language introduction. Inclusion tags also pair well with admin customisations like custom admin actions on list pages.

Frequently Asked Questions

What is the difference between a simple tag and an inclusion tag?

A simple_tag returns a single value (usually a string) directly into the template. An inclusion_tag returns a dict that becomes the context for a separate template fragment, which Django renders for you. Use simple_tag to compute a value and inclusion_tag to render reusable markup.

Why is my custom template tag not loading?

The three usual causes: the app isn't in INSTALLED_APPS, the templatetags/ directory is missing its __init__.py, or you didn't restart the dev server after creating the module. Also confirm your {% load %} uses the module filename, not the app name.

Does the inclusion tag template inherit the page context?

No. The fragment is rendered only with the dict your function returns. To access the page's context (such as request or user), add takes_context=True to the decorator and accept context as the first argument of your function.

Can an inclusion tag take keyword arguments?

Yes. The tag function is an ordinary Python function, so it accepts positional arguments, keyword arguments, and defaults. Call it like {% my_tag obj limit=5 css_class="featured" %} and the values map straight to the parameters.

Where should database queries go in an inclusion tag?

Inside the tag function, in Python — not in the template. Running queries in the function keeps your data access testable, makes caching straightforward, and prevents hidden query logic from creeping into the presentation layer.

When should I use {% include %} instead of an inclusion tag?

Use {% include %} when the partial needs no new Python logic and can rely on the surrounding context. Reach for an inclusion tag when the component needs to build its own context — for example by querying the database or transforming data — before rendering.

Building reusable Django components?

Inclusion tags are a small but powerful pattern for keeping Django templates DRY and data-driven. If you'd like an experienced team to design a maintainable template architecture or modernise an ageing Django app, MicroPyramid has delivered 50+ projects and has worked with Django since 2014. Explore our Django development services to see how we can help.

Share this article