Filters¶
Filters transform variable output using the pipe (|) syntax. You can chain multiple filters left-to-right, and many accept arguments after a colon (:).
Table of Contents¶
Basic Usage¶
Each | applies a filter to the value on its left. Filters are evaluated left-to-right: the output of one filter becomes the input of the next.
Filter Chaining¶
Filters apply left-to-right. The output of each filter becomes the input of the next:
{{ name|lower|capfirst }}
<!-- "Miki" β "miki" β "Miki" -->
{{ bio|striptags|truncatewords:20 }}
<!-- Strip HTML tags, then truncate to 20 words -->
{{ text|escape|truncatechars:50|upper }}
You can chain any number of filters:
{{ price|mult:1.2|add:2|floatformat:2|currency:"$" }}
<!-- price = 10 β 12.0 β 14.0 β "14.00" β "$14.00" -->
Filter Arguments¶
Filters accept the following argument types:
| Syntax | Type | Example |
|--------|------|---------|
| Unquoted | Variable lookup | value|filter:count |
| Double-quoted | String literal | value|filter:"hello" |
| Single-quoted | String literal | value|filter:'world' |
| Number | Integer/float literal | value|truncatewords:10 |
| Boolean | true/false | value|yesno:"yes,no" |
Real-world example β conditional greeting with fallback:
<h1>{{ user.name|default:"Guest"|capfirst }}</h1>
<span class="badge {% if user.is_premium|yesno:"yes,no" %}premium{% else %}free{% endif %}">
{{ user.plan|default:"Free" }}
</span>
Text Filters¶
| Filter | Description | Example |
|--------|-------------|---------|
| upper | Uppercase | {{ "hello"|upper }} |
| lower | Lowercase | {{ "HELLO"|lower }} |
| title | Title case | {{ "miki coder"|title }} |
| capfirst | Capitalize first letter | {{ "hello"|capfirst }} |
| truncatewords:N | Truncate to N words, appends ... | {{ body|truncatewords:30 }} |
| truncatechars:N | Truncate to N chars, appends ... | {{ title|truncatechars:50 }} |
| truncatechars_html:N | HTML-aware truncation to N chars, preserves tags | {{ html|truncatechars_html:100 }} |
| wordcount | Count words | {{ body|wordcount }} |
| linebreaks | Convert double newlines to <p> and single to <br> | {{ text|linebreaks }} |
| linebreaksbr | Convert all newlines to <br> | {{ text|linebreaksbr }} |
| striptags | Remove HTML tags | {{ html|striptags }} |
| repeat | Repeat a string N times | {{ "ha"|repeat:3 }} |
| range | Generate a range of integers | {{ 5|range }} |
| slugify | URL-friendly slug | {{ title|slugify }} |
| length_is:N | Test if length equals N | {{ items|length_is:0 }} |
truncatechars_html¶
Like truncatechars but respects HTML tags β tags are preserved in full and only visible text counts toward the limit:
Real-world blog excerpt:
linebreaks¶
Converts newlines into paragraphs (<p>) and standalone line breaks into <br>:
{{ "Line one\n\nLine two\nLine three"|linebreaks }}
<!-- Output: <p>Line one</p><p>Line two<br>Line three</p> -->
slugify¶
Converts text to a URL-safe slug (lowercase, hyphens, no special characters):
<a href="/posts/{{ post.title|slugify }}">{{ post.title }}</a>
<!-- title: "Hello World: A New Beginning!" β href="/posts/hello-world-a-new-beginning" -->
wordcount¶
Use with pluralize for dynamic labels:
HTML / Security Filters¶
| Filter | Description |
|--------|-------------|
| safe | Mark string as safe (no escaping) |
| escape | Force HTML escaping, even on SafeString |
Real-world CMS rendering:
<!-- Body is trusted HTML from the CMS -->
<div class="content">{{ post.body_html|safe }}</div>
<!-- User comments are always escaped -->
<div class="comment">{{ comment.text|escape }}</div>
List / Array Filters¶
| Filter | Description | Example |
|--------|-------------|---------|
| length | Length of list/string | {{ items|length }} |
| join:"," | Join with separator | {{ tags|join:", " }} |
| slice:"start:end" | Slice list | {{ items|slice:"0:5" }} |
| dictsort:"key" | Sort dict by key | {{ dict|dictsort:"name" }} |
| dictsortreversed:"key" | Reverse sort dict | {{ dict|dictsortreversed:"name" }} |
| sort | Sort array | {{ items|sort }} |
| unique | Deduplicate array | {{ tags|unique }} |
| random | Random item from array | {{ items|random }} |
| reverse | Reverse array or string | {{ items|reverse }} |
| split:"," | Split string into array | {{ csv|split:"," }} |
| replace:"old,new" | Replace substring | {{ text|replace:"foo,bar" }} |
slice¶
Slices an array or string like Python ([start:end]). Supports negative indices:
<!-- First 3 items -->
{{ items|slice:"0:3" }}
<!-- From index 2 onwards -->
{{ items|slice:"2:" }}
<!-- Last 2 items -->
{{ items|slice:"-2:" }}
join¶
Real-world tag cloud:
<div class="tag-cloud">
{% for tag in post.tags|join:", " %}
<span class="tag">{{ tag }}</span>
{% endfor %}
</div>
<!-- But better as: -->
<span class="tag-cloud">{{ post.tags|join:", " }}</span>
sort + unique¶
Real-world: deduplicated, sorted navigation:
split¶
Real-world: parsing comma-separated metadata:
<!-- post.keywords = "nature,landscape,autumn" -->
<div class="meta">
{% for kw in post.keywords|split:"," %}
<a href="/tag/{{ kw|slugify }}">{{ kw|capfirst }}</a>
{% endfor %}
</div>
Default Value Filters¶
| Filter | Description |
|--------|-------------|
| default:"fallback" | Use fallback for empty string, null, or undefined |
| default_if_none:"fallback" | Use fallback only for null/undefined |
| firstof:v1 v2 v3 | Return first truthy value |
default vs default_if_none¶
-
defaultuses the fallback for falsy values:null,undefined,"",0,false -
default_if_noneonly uses the fallback fornullandundefined
{{ ""|default:"empty" }} β "empty"
{{ ""|default_if_none:"N/A" }} β "" (empty string is not none)
{{ 0|default:"zero" }} β "zero"
{{ 0|default_if_none:"N/A" }} β 0 (zero is not none)
Real-world user profile:
<!-- Show "No bio yet" only when bio is truly empty -->
<p class="bio">{{ user.bio|default:"No bio yet." }}</p>
<!-- Distinguish between "never set" and "explicitly empty" -->
{% if user.display_name %}
<h2>{{ user.display_name|default_if_none:"Anonymous" }}</h2>
{% endif %}
Date and Time Filters¶
| Filter | Description | Example |
|--------|-------------|---------|
| date:"Y-m-d" | Format date (Django-style tokens) | {{ d|date:"Y-m-d" }} |
| time:"H:i" | Format time | {{ d|time:"H:i" }} |
| date_format:"yyyy-MM-dd" | Format date (date-fns tokens) | {{ d|date_format:"yyyy-MM-dd" }} |
| strftime:"PPpp" | Format date (date-fns tokens) | {{ now|strftime:"PPpp" }} |
| timesince | Time since date | {{ created|timesince }} |
| timeuntil | Time until date | {{ start|timeuntil }} |
| ago | Time since date, human-readable | {{ created|ago }} |
| until | Time until date, human-readable | {{ start|until }} |
| time_diff:other_date | Difference between two dates | {{ start|time_diff:end }} |
Date format tokens (date and time filters)¶
The date filter uses Django-style format tokens with longest-first matching:
| Token | Output |
|-------|--------|
| Y | 4-digit year |
| y | 2-digit year |
| m | Month number (no pad) |
| n | Month number (no pad) |
| d | Day number (no pad) |
| j | Day number (no pad) |
| H | 24-hour hour (no pad) |
| G | 24-hour hour (no pad) |
| i | Minutes |
| s | Seconds |
| F | Long month name |
| D | Short day name |
| M | Long month name |
date_format and strftime (date-fns tokens)¶
For ISO and date-fns patterns:
{{ post.published_at|date_format:"yyyy-MM-dd" }} β "2026-08-31"
{{ post.published_at|strftime:"PPpp" }} β "Aug 31, 2026 at 10:30 PM"
{{ event.date|strftime:"EEEE, MMMM do yyyy, h:mm a" }} β "Sunday, August 31st 2026, 10:30 PM"
timesince / timeuntil¶
Returns a human-readable time difference:
{{ post.created|timesince }} β "2 hours"
{{ post.created|timesince:other_date }} β "3 days" (relative to other_date)
{{ event.date|timeuntil }} β "5 days"
ago / until¶
More human-readable relative time strings:
<!-- "2 days ago", "just now", "3 months ago" -->
<span class="timestamp">{{ comment.created|ago }}</span>
<!-- "2 days", "3 weeks", "1 month" -->
<time datetime="{{ event.date|date:'c' }}">{{ event.date|until }}</time>
Real-world blog post metadata:
<article class="post">
<header>
<h1>{{ post.title }}</h1>
<time class="posted-at">
Published {{ post.published_at|date:"F j, Y" }} ({{ post.published_at|ago }})
</time>
</header>
<div class="content">
{{ post.body|linebreaks }}
</div>
</article>
Numeric / Math Filters¶
| Filter | Description | Example |
|--------|-------------|---------|
| add:N | Add number | {{ count\|add:1 }} |
| sub:N | Subtract number | {{ total\|sub:tax }} |
| mult:N | Multiply number | {{ price\|mult:1.2 }} |
| divisibleby:N | Test divisibility | {{ i\|divisibleby:2 }} |
| mod:N | Modulo | {{ i\|mod:3 }} |
| floatformat:N | Format float | {{ price\|floatformat:2 }} |
| square | Square a number | {{ n\|square }} |
| sqrt | Square root | {{ n\|sqrt }} |
| abs | Absolute value | {{ delta\|abs }} |
| round:N | Round to N decimals | {{ price\|round:2 }} |
| floor | Floor | {{ ratio\|floor }} |
| ceil | Ceiling | {{ ratio\|ceil }} |
| min:N | Minimum of value and arg | {{ temp\|min:0 }} |
| max:N | Maximum of value and arg | {{ temp\|max:100 }} |
| sum | Sum array | {{ numbers\|sum }} |
| average | Average array | {{ scores\|average }} |
floatformat behavior¶
| arg | behavior |
|-----|----------|
| (none) | 1 decimal (3.4) |
| 0 | 0 decimals (3) |
| 1 | 1 decimal (3.4) |
| 2 | 2 decimals (3.40) |
| -1 | all decimals, trimmed |
Real-world pricing example¶
<td class="price">
${{ item.price|mult:item.qty|floatformat:2 }}
</td>
<!-- If not on sale, apply discount -->
{% if not item.on_sale %}
<td>{{ item.base_price|mult:0.9|floatformat:2 }}</td>
{% endif %}
Data Formatting Filters¶
| Filter | Description | Example |
|--------|-------------|---------|
| currency:"$" | Format as currency | {{ price|currency:"$" }} |
| phone_number | Format as phone number | {{ raw|phone_number }} |
| email | Format as mailto link | {{ address|email }} |
| url | Format as URL | {{ domain|url }} |
| mask:"*" | Mask string, show last 4 chars | {{ card|mask }} |
| whatsapp_link:"msg" | Generate WhatsApp link | {{ phone|whatsapp_link }} |
| credit_card | Format as credit card | {{ raw|credit_card }} |
| ssn | Format as SSN | {{ raw|ssn }} |
| ip_address | Format as IP address | {{ raw|ip_address }} |
| uuid | Generate UUID (no input needed) | {{ x|uuid }} |
| filesizeformat | Human-readable file size | {{ bytes|filesizeformat }} |
| yesno:"yes,no,maybe" | Convert bool to string | {{ active|yesno:"Active,Inactive" }} |
| pluralize:"s" | Pluralize based on count | {{ count|pluralize }} |
| urlencode | URL encode | {{ text|urlencode }} |
| escapeuri | URI encode | {{ text|escapeuri }} |
| stringformat:"%s" | sprintf-style formatting | {{ name|stringformat:"%s" }} |
| cut:"text" | Remove substring | {{ text|cut:"foo" }} |
| addslashes | Escape quotes | {{ text|addslashes }} |
| removetags:"p,div" | Remove specific tags | {{ html|removetags:"p,div" }} |
| trans | Translate via i18n | {{ "hello"|trans }} |
| regroup:"attr" | Group list by attribute | {{ items|regroup:"category" }} |
| json | JSON-encode value | {{ obj|json }} |
| urlize | Convert URLs to links | {{ text|urlize }} |
phone_number¶
Formats a 10-digit US phone number as (123) 456-7890. Handles 11-digit numbers with leading 1 as +1 (123) 456-7890:
{{ "1234567890"|phone_number }} β "(123) 456-7890"
{{ "11234567890"|phone_number }} β "+1 (123) 456-7890"
credit_card¶
Formats a credit card number with dashes every 4 digits:
ssn¶
Formats a 9-digit Social Security Number:
mask¶
Masks all but the last 4 characters. Default mask character is *:
<!-- Mask a phone number or credit card in display -->
<span class="masked">{{ user.phone|mask }}</span>
<!-- Custom mask character -->
{{ "1234567890"|mask:"#" }} β "######7890"
url (with safe filter)¶
email¶
<a href="{{ user.email|email }}">{{ user.email }}</a>
<!-- β <a href="mailto:user@example.com">user@example.com</a> -->
whatsapp_link¶
Generates a WhatsApp link with an optional pre-filled message:
<a href="{{ phone|whatsapp_link:"Hello! I'd like to know more." }}" target="_blank">
Chat on WhatsApp
</a>
json (safe for <script> blocks)¶
Safely serializes data to JSON for client-side consumption:
Note:
jsonoutput is marked safe automatically. Use|safein the template only to signal intent β the engine handles it correctly either way.
stringformat¶
{{ 3.14159|stringformat:".2f" }} β "3.14"
{{ count|stringformat:"04d" }} β "0042"
{{ name|stringformat:"%s" }} β "Alice"
cut¶
Removes all occurrences of a substring:
pluralize¶
Real-world item count:
<p>{{ cart.items|length }} item{{ cart.items|length|pluralize }} in your cart</p>
<!-- "1 item in your cart" / "3 items in your cart" -->
<!-- Custom suffixes -->
<p>{{ count|pluralize:"y,ies" }} comment{{ count|pluralize:"y,ies" }}</p>
yesno¶
Real-world status badge:
<span class="status {{ user.is_active|yesno:"active,inactive" }}">
{{ user.is_active|yesno:"Active,Inactive" }}
</span>
Encoding Filters¶
| Filter | Description | Example |
|--------|-------------|---------|
| base64_encode | Base64 encode | {{ text|base64_encode }} |
| base64_decode | Base64 decode | {{ text|base64_decode }} |
Real-world: embedding a CSRF token in a header¶
Time-Ago Filters¶
| Filter | Description | Example |
|--------|-------------|---------|
| time_diff:other | Human-readable time difference | {{ start|time_diff:end }} |
| ago | Time since date | {{ created|ago }} |
| until | Time until date | {{ start|until }} |
ago β human-readable "time since"¶
<!-- "just now", "5 minutes ago", "2 hours ago", "3 days ago", "1 year ago" -->
<span class="time-ago">{{ post.created_at|ago }}</span>
until β human-readable "time remaining"¶
Built-in Library Filters¶
humanize¶
Available after {% load humanize %}:
| Filter | Description | Example |
|--------|-------------|---------|
| intcomma | Add comma separators | {{ views|intcomma }} β 1,234 |
| intword | Convert to human word | {{ 1000000|intword }} β 1.0 million |
| apnumber | Convert 0-19 to words | {{ 3|apnumber }} β three |
| ordinal | Add ordinal suffix | {{ 1|ordinal }} β 1st |
| naturalday | Convert date to relative day | {{ date|naturalday }} β today |
Usage:
cache¶
The cache library provides a {% cache %} tag, not a filter. See Tags: Library Loading for details.
lorem¶
The lorem library provides a {% lorem %} tag and a lorem filter. These are auto-activated (no {% load %} needed).
Writing Custom Filters¶
You can register your own filters. See the Custom Filters guide for details.
Filter Documentation¶
repeat¶
Signature: repeat(value, count)
- Purpose: Returns a new string consisting of
valuerepeatedcounttimes. - Parameters:
valueβ Any value that can be converted to a string (null/undefined become empty string).countβ Number of repetitions, parsed as integer; nonβpositive yields empty string.- Implementation: Uses native
String.prototype.repeatfor optimal V8 performance. - Example:
{{ "ab"|repeat:3 }} => "ababab"
range¶
Signature: range(end, start?)
- Purpose: Generates an array of integers from
start(inclusive) toend(exclusive) with stepβ―1. - Parameters:
endβ Upper bound (exclusive), parsed as integer.startβ Optional lower bound, defaults to0.- Implementation: Simple forβloop building a JavaScript array.
- Example:
{% for i in 5|range %}{{ i }}{% endfor %}outputs0 1 2 3 4.
Additional Django Filters¶
The following filters complete parity with the vscode-django-support extension:
String formatting¶
| Filter | Signature | Example | Output |
|---|---|---|---|
center |
center(width) |
"hello"|center:"15" |
" hello " |
ljust |
ljust(width) |
"hi"|ljust:"10" |
"hi " |
rjust |
rjust(width) |
"hi"|rjust:"10" |
" hi" |
wordwrap |
wordwrap(width) |
"hello world"|wordwrap:"5" |
wrapped lines |
linenumbers |
linenumbers |
"a\nb"|linenumbers |
"1. a\n2. b" |
make_list |
make_list |
"hello"|make_list |
["h","e","l","l","o"] |
slugify |
already supported | β | β |
HTML / escaping¶
| Filter | Signature | Example | Output |
|---|---|---|---|
escapejs |
escapejs |
"<b>"|escapejs |
\x3Cb\x3E |
force_escape |
force_escape |
"<b>"|force_escape |
<b> |
fix_ampersands |
fix_ampersands |
"A & B"|fix_ampersands |
A & B |
safeseq |
safeseq |
list|safeseq |
marks all items safe |
Sequence / object¶
| Filter | Signature | Example | Output |
|---|---|---|---|
first |
first |
"abc"|first |
"a" |
last |
last |
"abc"|last |
"c" |
length_is |
already supported | β | β |
random |
already supported | β | β |
slice |
already supported | β | β |
Numbers / dates¶
| Filter | Signature | Example | Output |
|---|---|---|---|
apnumber |
apnumber |
"5"|apnumber |
"five" |
intcomma |
intcomma |
"1000"|intcomma |
"1,000" |
intword |
intword |
"1500000"|intword |
"1.5 million" |
ordinal |
ordinal |
"1"|ordinal |
"1st" |
naturalday |
naturalday |
"2026-01-01"|naturalday |
"today" / "yesterday" / date |
get_digit |
get_digit(index) |
"12345"|get_digit:"2" |
"3" |
timeutil |
timeutil(date?) |
alias for timeuntil |
β |
Lists / grouping¶
| Filter | Signature | Example | Output |
|---|---|---|---|
unordered_list |
unordered_list |
items|unordered_list |
<ul>...</ul> |
truncatewords_html |
truncatewords_html(count) |
HTML string | truncated HTML |
URLs / encoding¶
| Filter | Signature | Example | Output |
|---|---|---|---|
urlizetrunc |
urlizetrunc(len) |
URL string | <a href="...">...</a> |
iriencode |
iriencode |
"hello world"|iriencode |
"hello%20world" |
Misc¶
| Filter | Signature | Example | Output |
|---|---|---|---|
pprint |
pprint |
obj|pprint |
pretty-printed JSON |
phone2numeric |
phone2numeric |
"1-800-CALL"|phone2numeric |
"1-800-2255" |
STATIC_PREFIX |
STATIC_PREFIX |
"css/app.css"|STATIC_PREFIX |
"/static/css/app.css" |
These filters are covered by unit tests in tests/filters.test.js and are documented here for developers and template authors.