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
- Why you need a templatetags package and load tag
- Writing a filter with @register.filter
- Applying a filter as value|filter and value|filter:arg
- Returning a value with @register.simple_tag
- Rendering a snippet with @register.inclusion_tag
- That tags and filters are just plain Python functions
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 hereInside 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 world2 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 Lovelace3 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.filter — transform a value, used with the pipe as value|filter.
- @register.simple_tag — return a computed value, used as a {% tag %}.
- @register.inclusion_tag — render a sub-template from a returned context dict.
- All three are ordinary Python functions registered on a template.Library().
# @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:
# abababCommon 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 / Syntax | What It Does |
|---|---|
| register = template.Library() | Create the library tags and filters register on |
| @register.filter | Register a function used as value|name |
| @register.simple_tag | Register 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
- Previous: Custom Managers & QuerySets
- Next: Checkpoint: Models & the ORM — Combine models, advanced queries, aggregation and managers — then a quiz
- Quick reference: Django cheat sheet
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.