Custom Template Tags & Filters

Extend the template language with your own filters, simple tags and inclusion tags.

When a template needs logic the built-in tags can't express — formatting a value a particular way, rendering a reusable widget, or querying a small bit of data — you write your own tag or filter. They live in a templatetags/ package inside an app and are pulled in with {% load %}.

Three flavours, in order of how often you'll reach for them

  • Filter — transforms a single value: {{ price|money }}.
  • Simple tag — runs a function and outputs the result: {% active_url 'home' %}.
  • Inclusion tag — renders a small sub-template with its own context: {% pagination page_obj %}.
# myapp/templatetags/money.py
from django import template

register = template.Library()

@register.filter
def money(value):
    return f'${value:,.2f}'

# In a template:
# {% load money %}
# {{ product.price|money }}   ->  $1,299.00

The templatetags folder must contain an __init__.py, and the app must be in INSTALLED_APPS, or {% load %} won't find your library.

Example

Example · python
# blog/templatetags/blog_extras.py
from django import template
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.db.models import Count

register = template.Library()


# 1) A filter with an argument.
@register.filter
def truncate_smart(text, length=50):
    text = str(text)
    return text if len(text) <= length else text[:length].rsplit(' ', 1)[0] + '\u2026'


# 2) A simple tag that needs the request context (highlight the active nav link).
@register.simple_tag(takes_context=True)
def active(context, url_name):
    from django.urls import reverse
    request = context['request']
    return 'active' if request.path == reverse(url_name) else ''


# 3) A filter that safely builds HTML from user data.
@register.filter
def badge(label, kind='default'):
    # format_html ESCAPES label — safe even if it contains <script>.
    return format_html('<span class="badge badge-{}">{}</span>', kind, label)


# 4) An inclusion tag: a reusable, self-contained widget.
@register.inclusion_tag('blog/_tag_cloud.html')
def tag_cloud(limit=20):
    tags = (
        Tag.objects
        .annotate(n=Count('posts'))
        .filter(n__gt=0)
        .order_by('-n')[:limit]
    )
    return {'tags': tags}

# Usage in a template:
#   {% load blog_extras %}
#   <a class="{% active 'home' %}" href="/">Home</a>
#   {{ post.excerpt|truncate_smart:80 }}
#   {{ 'Beta'|badge:'warning' }}
#   {% tag_cloud limit=15 %}

When to use it

  • A developer creates a custom template tag to render a sidebar widget with its own database query, keeping views clean.
  • A team writes a custom filter to convert Markdown text to safe HTML for use throughout all templates.
  • A developer creates an inclusion tag to render a reusable notification badge component with its own context.

More examples

Simple custom filter

Registers a custom filter that estimates reading time from word count for use as {{ post.body|reading_time }}.

Example · python
# blog/templatetags/blog_extras.py
from django import template

register = template.Library()

@register.filter(name="reading_time")
def reading_time(text):
    words = len(text.split())
    minutes = max(1, words // 200)
    return f"{minutes} min read"

Simple tag that returns a value

Outputs the site name from settings; simpler than a context processor for single-use template values.

Example · python
from django import template
from django.conf import settings

register = template.Library()

@register.simple_tag
def site_name():
    return getattr(settings, "SITE_NAME", "My Site")

# Usage in template: {% site_name %}

Inclusion tag with own context

Renders a categories partial template with its own queryset, callable from any template without view changes.

Example · python
from django import template
from ..models import Category

register = template.Library()

@register.inclusion_tag("blog/partials/categories.html")
def show_categories():
    return {"categories": Category.objects.all()}

# Usage: {% show_categories %}

Discussion

  • Be the first to comment on this lesson.