Custom Template Tags & Filters

Reviewed & published by Brayan K

A custom template tag or filter is a plain Python function, registered with Django's template library, that lets your own templates transform a value or compute new output beyond the built-in tags.

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 build a templatetags package and write all three kinds of extension: a filter that reshapes a value, a simple_tag that returns a computed value, and an inclusion_tag that renders a small template from a returned context — then load and use each one in a template.

What You'll Learn in This Lesson

1 Custom Filters with @register.filter

A filter is a function that takes a value, optionally one argument, and returns a transformed value. You write it in a templatetags package, decorate it with @register.filter, and apply it in a template with the pipe syntax. First you need the package itself — a templatetags folder with an empty __init__.py inside your app.

blog/
├── __init__.py
├── models.py
├── views.py
└── templatetags/
    ├── __init__.py          # empty file — makes it a package
    └── blog_extras.py       # your tags and filters live here

Inside blog_extras.py you create a library and register your filters on it:

# blog/templatetags/blog_extras.py
from django import template

register = template.Library()

@register.filter
def truncate_chars(value, count):
    if len(value) <= count:
        return value
    return value[:count] + "..."

@register.filter
def upper_first(value):
    return value[:1].upper() + value[1:]

Then load the library at the top of a template and apply the filters with the pipe:

{% load blog_extras %}

{{ post.body|truncate_chars:10 }}   {# filter with an argument #}
{{ name|upper_first }}              {# filter with no argument  #}
# A custom filter is a plain Python function decorated with @register.filter.
# In a template it's used as {{ value|filter }} or {{ value|filter:arg }}.
# Below is the REAL function logic from blog_extras.py, run for real.

def truncate_chars(value, count):       # @register.filter
    if len(value) <= count:
        return value
    return value[:count] + "..."

def upper_first(value):                 # @register.filter
    return value[:1].upper() + value[1:]

# Call them exactly like Django would when rendering the template
print(truncate_chars("Django template filters are powerful", 10))
print(truncate_chars("Short", 10))
print(upper_first("hello world"))

# Template usage would be:
#   {{ post.body|truncate_chars:10 }}
#   {{ name|upper_first }}

# EXPECTED:
# Django tem...
# Short
# Hello world

# ✅ Expected output:
# Django tem...
# Short
# Hello world

2 Returning a Value with @register.simple_tag

A simple_tag is a function that returns a value and is invoked with the percent-brace tag syntax rather than the pipe. Unlike a filter, it can take any number of positional and keyword arguments, which makes it perfect for computations that need more than one input.

# blog/templatetags/blog_extras.py
from django import template

register = template.Library()

@register.simple_tag
def discounted_price(price, percent):
    return round(price - (price * percent / 100), 2)

@register.simple_tag
def full_name(first, last):
    return f"{first} {last}".strip()

Call it in a template with the tag syntax — and you can capture the result into a variable with as:

{% load blog_extras %}

Now {% discounted_price product.price 25 %}

{% full_name user.first_name user.last_name as name %}
Welcome, {{ name }}!
# @register.simple_tag wraps a plain function that RETURNS a value.
# In a template it's called as {% mytag arg %} or {% mytag a b as result %}.
# Here is the real function logic, run for real.

def discounted_price(price, percent):   # @register.simple_tag
    return round(price - (price * percent / 100), 2)

def full_name(first, last):             # @register.simple_tag
    return f"{first} {last}".strip()

# Calling the tag function directly, exactly what Django does:
print(discounted_price(100, 25))
print(discounted_price(49.99, 10))
print(full_name("Ada", "Lovelace"))

# Template usage would be:
#   {% discounted_price product.price 25 %}
#   {% full_name user.first_name user.last_name as name %}

# EXPECTED:
# 75.0
# 44.99
# Ada Lovelace

# ✅ Expected output:
# 75.0
# 44.99
# Ada Lovelace

3 Rendering a Snippet with @register.inclusion_tag

An inclusion_tag renders a small template instead of returning a plain value. You decorate the function with @register.inclusion_tag("some_template.html"); the function builds and returns a context dict, and Django renders the named template with that context, inserting the result wherever the tag appears.

# blog/templatetags/blog_extras.py
from django import template

register = template.Library()

@register.inclusion_tag("blog/tags.html")
def show_tags(tags):
    # The dict this returns becomes the context for tags.html
    return {"tags": tags, "count": len(tags)}

The small template it renders, blog/tags.html:

{# blog/templates/blog/tags.html #}
<ul>
{% for t in tags %}  <li>{{ t }}</li>
{% endfor %}</ul>
<p>{{ count }} tags</p>

And calling it from any page is a single tag — no HTML strings in your Python:

{% load blog_extras %}

{% show_tags post.tags %}
# @register.inclusion_tag renders a small template using the context dict
# the function RETURNS. The function's job is to build that context.
# We run the real function and render its dict with a tiny template engine.

def show_tags(tags):                    # @register.inclusion_tag("tags.html")
    return {"tags": tags, "count": len(tags)}

# A stand-in for the small template tags.html:
#   <ul>{% for t in tags %}<li>{{ t }}</li>{% endfor %}</ul>
#   <p>{{ count }} tags</p>
def render(context):
    lines = ["<ul>"]
    for t in context["tags"]:
        lines.append(f"  <li>{t}</li>")
    lines.append("</ul>")
    lines.append(f"<p>{context['count']} tags</p>")
    return "\n".join(lines)

context = show_tags(["django", "python", "web"])
print(render(context))

# Template usage would be:
#   {% show_tags post.tags %}

# EXPECTED:
# <ul>
#   <li>django</li>
#   <li>python</li>
#   <li>web</li>
# </ul>
# <p>3 tags</p>

# ✅ Expected output:
# <ul>
#   <li>django</li>
#   <li>python</li>
#   <li>web</li>
# </ul>
# <p>3 tags</p>

🎯 Your Turn: Pass the Value First

A filter always receives the piped value as its first argument. Fill in the blank so the filter is called with word first, then the argument.

# 🎯 YOUR TURN: a @register.filter takes the value as its FIRST argument.
# Replace ___ so the filter receives the value first, then the argument.

def repeat(value, times):               # @register.filter used as {{ s|repeat:3 }}
    return value * times

word = "ab"
# In a template, {{ word|repeat:3 }} calls repeat(word, 3)
result = repeat(___, 3)
print(result)

# EXPECTED OUTPUT:
# ababab

Common Errors (And How to Fix Them)

❌ "'blog_extras' is not a registered tag library"

Django can't find the library — usually a missing templatetags/__init__.py, a wrong filename, or you forgot to restart the server.

✅ Fix: ensure templatetags/__init__.py exists, the module name matches what you {% load %}, the app is in INSTALLED_APPS, then restart the server.

You used the filter without loading its library at the top of the template.

✅ Fix: add {% load blog_extras %} to the top of the template before you use the filter.

❌ "takes 1 positional argument but 2 were given"

You passed a colon argument to a filter whose function only declares the value parameter.

✅ Fix: add a second parameter to the function, e.g. def truncate_chars(value, count):, so it can accept the argument after the colon.

📋 Quick Reference

Decorator / SyntaxWhat It Does
register = template.Library()Create the library tags and filters register on
@register.filterRegister a function used as value|name
@register.simple_tagRegister a function that returns a value
@register.inclusion_tag("t.html")Render a sub-template from a returned context dict
{% load blog_extras %}Make a library's tags and filters available in a template

🏆 Mini Challenge: Run a Tag and Build Its Usage

Write a money simple_tag, run it for real, then build the two template lines that load the library and call the tag.

# 🏆 Mini Challenge: build the {% load %} + usage a template needs.
# A simple_tag returns a value; here we run the real tag function AND
# generate the template lines that would call it.

def money(amount, symbol="$"):          # @register.simple_tag
    return f"{symbol}{amount:.2f}"

# Run the tag function for real, like Django does when rendering:
print(money(7))
print(money(1234.5, "£"))

# Now build the template snippet that loads and uses the tag library:
load_line = "{% load store_extras %}"
usage_line = "{% money product.price %}"
print(load_line)
print(usage_line)

# EXPECTED:
# $7.00
# £1234.50
# {% load store_extras %}
# {% money product.price %}

❓ Frequently Asked Questions

Lesson complete — you can extend the template language!

You built a templatetags package, wrote a @register.filter applied with the pipe, a @register.simple_tag that returns a value, and a @register.inclusion_tag that renders a sub-template from a context dict — and you know they're all just plain Python functions you load with {% load %}.

Practice quiz

Where must custom template tags and filters live to be discoverable?

  • In settings.py
  • Inside the project's urls.py
  • In a file named tags.py at the project root
  • In a templatetags package inside an installed app

Answer: In a templatetags package inside an installed app. Django only scans a templatetags package (with __init__.py) inside an installed app.

What object do you create to register tags and filters on?

  • template.Registry()
  • django.Tags()
  • template.Library()
  • register.Module()

Answer: template.Library(). You create register = template.Library() and decorate functions with it.

Which decorator registers a function used with the pipe, as value|name?

  • @register.filter
  • @register.simple_tag
  • @register.inclusion_tag
  • @register.pipe

Answer: @register.filter. @register.filter registers a filter applied with the pipe syntax in a template.

For a filter used as {{ post.body|truncate_chars:10 }}, what are the function's arguments?

  • truncate_chars(10, post.body)
  • truncate_chars(post.body, 10)
  • truncate_chars(10) only
  • truncate_chars(post.body) only

Answer: truncate_chars(post.body, 10). The piped value is the first argument and the colon argument is the second: truncate_chars(post.body, 10).

How is a simple_tag invoked in a template?

  • With the pipe, as value|tag
  • Inside double braces, as {{ tag }}
  • With the tag syntax, as {% tag arg %}
  • It cannot be called from a template

Answer: With the tag syntax, as {% tag arg %}. A simple_tag is called with the {% tag ... %} syntax, never with the pipe.

What advantage does simple_tag have over a filter?

  • It can take any number of positional and keyword arguments
  • It runs before the template loads
  • It does not need {% load %}
  • It can only return None

Answer: It can take any number of positional and keyword arguments. Unlike a filter (value plus at most one arg), a simple_tag accepts many positional and keyword args.

What does a function decorated with @register.inclusion_tag return?

  • An HttpResponse object
  • A finished HTML string
  • Nothing — it prints directly
  • A context dict used to render a named template

Answer: A context dict used to render a named template. An inclusion_tag returns a context dict, and Django renders the named template with it.

Which tag must be loaded before using tags from blog_extras.py?

  • {% import blog_extras %}
  • {% load blog_extras %}
  • {% use blog_extras %}
  • {% require blog_extras %}

Answer: {% load blog_extras %}. {% load blog_extras %} makes that library's tags and filters available in the template.

Which error means Django could not find the library you tried to load?

  • TemplateDoesNotExist
  • NoReverseMatch
  • 'blog_extras' is not a registered tag library
  • ImproperlyConfigured: missing register

Answer: 'blog_extras' is not a registered tag library. That error usually means a missing __init__.py, wrong filename, app not installed, or no server restart.

Which statement about custom tags and filters is true?

  • They are all ordinary Python functions registered on a template.Library()
  • They must be written in a special DSL, not Python
  • Filters can return only strings, never numbers
  • An inclusion_tag returns a raw HTML string

Answer: They are all ordinary Python functions registered on a template.Library(). Filters, simple_tags, and inclusion_tags are plain Python functions registered on a Library().

Continue this course

Frequently asked questions

What is the difference between a filter and a simple_tag?

A filter is a function that transforms a single value and is applied with the pipe syntax inside double-brace output, optionally taking one extra argument after a colon. A simple_tag is a function that returns a value and is invoked with the percent-brace tag syntax, accepting any number of positional and keyword arguments. Reach for a filter to tweak a value you are already printing, and a simple_tag when you need to compute something fresh or pass several arguments.

Where do custom template tags and filters have to live?

They must live in a Python module inside a templatetags package — a directory named templatetags containing an empty __init__.py — that sits directly inside one of your installed apps. Django only scans that exact location, so a module placed anywhere else will not be discoverable. Once the file exists you load its tags into a template before using them.

Why do I get 'is not a registered tag library'?

That error means the load tag could not find the library name you asked for. The usual causes are a missing templatetags/__init__.py file, a module filename that does not match the name you tried to load, the app not being listed in INSTALLED_APPS, or a forgotten server restart after creating the package. Fix the package layout and restart the development server so Django re-scans for tag libraries.

When should I use an inclusion_tag instead of a simple_tag?

Use an inclusion_tag when the tag's job is to render a chunk of HTML rather than return a plain value. The decorated function returns a context dictionary, and Django renders a small template you name with that context, returning the finished markup. This keeps repeated snippets — a star rating, a nav menu, a card — in their own template file instead of building HTML strings in Python.