Filter concepts by levelShowing all levels.

Django · Templates

Templates

Inheritance, autoescaping, custom tags, and the built-ins every form needs

  • {% extends %} must be firsta block falls back to the parent if unfilled
  • Autoescaping is on by default|safe/mark_safe() only for trusted output
  • templatetags/ needs {% load %}per-template, not project-wide
  • {% csrf_token %} in every POST formnever hard-code a static/url path either

1.Template inheritance

{% extends "base.html" %} (first tag, required) makes a template inherit a parent's structure; {% block name %}...{% endblock %} marks a section the child can override; {% include "x.html" %} renders a separate template in place, independently.

Key features

  • {% extends %} must be the very first tag in the child template — nothing, not even a comment, before it
  • A block not overridden in the child renders whatever the parent put inside it
  • {{ block.super }} inside an overriding block inserts the parent's version of that same block, instead of replacing it entirely
  • {% include "x.html" %} renders independently — it does not participate in the extends/block chain at all
  • {% include "x.html" with name="Jane" only %} passes a scoped context; without only, the including template's full context is still visible

Inheritance vs. inclusion

Inheritance vs. inclusion
TagRelationship to the other templateShares context?
{% extends %}child fills labeled blocks in a parentyes — same rendering pass
{% block %}names one overridable section—
{{ block.super }}inserts the parent's version of the current block—
{% include %}renders a separate template in placeyes by default; only limits it
A block left unfilled falls through to the parent

base.html

defines {% block content %}default{% endblock %}

child.html

{% extends "base.html" %}, does not override content

Rendered output

shows "default" — the parent's own block content

  1. base.html — defines {% block content %}default{% endblock %}
  2. child.html — {% extends "base.html" %}, does not override content
  3. Rendered output — shows "default" — the parent's own block content

Command

html
{% extends "base.html" %}
{% block content %}
  {% include "articles/_comment_list.html" with comments=article.comments %}
{% endblock %}

{% extends %} must be the first tag; a block not overridden falls back to the parent's content; {{ block.super }} adds to it instead of replacing it; {% include %} renders a separate template independently, sharing context by default.

2.Autoescaping and safe strings

Every {{ variable }} is HTML-escaped by default (< > & ' " become entities), so template output is safe against XSS unless deliberately opted out — via the |safe filter, mark_safe() in Python, or {% autoescape off %}. Marking user-supplied content safe reintroduces exactly the vulnerability escaping exists to prevent.

Key features

  • Autoescaping is on by default for the whole template — {{ value }} always escapes unless told otherwise
  • Escaped characters: < > ' " & become &lt; &gt; &#x27; &quot; &amp;
  • |safe on a value opts it out of escaping for that one output — the value itself must already BE safe HTML
  • mark_safe() does the same thing from Python code, marking a string safe before it ever reaches the template
  • {% autoescape off %}...{% endautoescape %} disables escaping for a whole block, not just one variable

Opting out of autoescaping

Opting out of autoescaping
MechanismScopeWhere used
{{ value|safe }}one variable, one outputinside a template
mark_safe(value)the string itself, wherever it's usedPython code (view, model method)
{% autoescape off %}everything inside the blockinside a template
Autoescaping is the default guard, |safe opts one value out
explicitopt-out

{{ value }}

autoescaping

on by default — escapes < > & ' "

escaped HTML

safe, even if value contains <script>

|safe / mark_safe()

opts out — value must ALREADY be trusted HTML

  • {{ value }}
    • leads to autoescaping
    • on error, leads to |safe / mark_safe() (explicit opt-out)
  • autoescaping — on by default — escapes < > & ' "
    • leads to escaped HTML
  • escaped HTML — safe, even if value contains <script>
  • |safe / mark_safe() — opts out — value must ALREADY be trusted HTML

Command

python
from django.utils.safestring import mark_safe

def render_markdown(text):
    html = markdown.markdown(text)
    return mark_safe(html)   # trusted: generated by a known library, not raw user input

Autoescaping is on by default and safe by default — |safe/mark_safe() must only ever be applied to output from a KNOWN, TRUSTED transformation, never to raw user input.

3.Custom tags, filters, and template loading

A custom filter is a plain function registered with @register.filter; a simple custom tag is a function registered with @register.simple_tag. Both live in an app's templatetags/ package and are brought into a template with {% load name %}. APP_DIRS=True makes Django search DIRS first, then each installed app's templates/ folder.

Key features

  • Custom tags/filters live in <app>/templatetags/<name>.py — that app must be in INSTALLED_APPS
  • @register.filter wraps a function taking 1-2 args (the value, an optional argument) — used as {{ value|myfilter:arg }}
  • @register.simple_tag wraps a function that returns a string — used as {% mytag arg %}, and supports {% mytag arg as var %}
  • {% load myapp_tags %} is required in every template that uses tags/filters from that library — not automatic
  • APP_DIRS=True searches DIRS first (in order), then each app's templates/ (in INSTALLED_APPS order) — first match wins

Custom filter vs. custom simple tag

Custom filter vs. custom simple tag
Aspect@register.filter@register.simple_tag
Template syntax{{ value|name:arg }}{% name arg %}
Argumentsthe piped value, plus one optional argany number, positional or keyword
Can access full contextnoyes, with takes_context=True
Store result in a variablenoyes, with {% name arg as var %}

Command

python
# blog/templatetags/blog_extras.py
from django import template
register = template.Library()

@register.simple_tag
def current_year():
    from django.utils import timezone
    return timezone.now().year

Custom tags/filters live in <app>/templatetags/, need {% load name %} in every template that uses them; APP_DIRS searches DIRS first, then each app's templates/ in INSTALLED_APPS order — first match wins.

4.csrf_token, static, and url tags

{% csrf_token %} inserts a hidden field required on every POST form; {% static "path" %} (after {% load static %}) resolves a static file to its real URL; {% url "name" %} (built in, no load needed) resolves a URL by name via reverse() — none of the three should ever be replaced with a hard-coded equivalent.

Key features

  • {% csrf_token %} renders <input type="hidden" name="csrfmiddlewaretoken" value="...">, required inside every POST <form>
  • {% csrf_token %} needs the csrf context processor enabled (it is, by default) — RequestContext, not a plain dict, must reach the template
  • {% static %} requires {% load static %} first — url and csrf_token are always available, no load needed
  • {% static "images/logo.png" %} resolves via STATIC_URL, following the same lookup collectstatic itself uses
  • {% url "app:name" arg=value %} is the template equivalent of reverse() — same namespace rules apply

The three tags at a glance

The three tags at a glance
TagNeeds {% load %}Resolves to
{% csrf_token %}no — always availablea hidden input with a signed token
{% static "path" %}yes — {% load static %}STATIC_URL + path
{% url "name" %}no — always availablereverse("name", ...)
Three tags, all resolved fresh at render time

{% csrf_token %}

a signed, per-session hidden field

{% static 'path' %}

the real deployed URL, via STATIC_URL

{% url 'name' %}

reverse() by name, not a hard-coded path

  • {% csrf_token %} — a signed, per-session hidden field
  • {% static 'path' %} — the real deployed URL, via STATIC_URL
  • {% url 'name' %} — reverse() by name, not a hard-coded path

Command

html
<a href="{% url 'articles:detail' pk=article.pk %}">{{ article.title }}</a>

Never hard-code what these three tags compute: {% csrf_token %} in every POST form, {% static %} for asset paths, {% url %} for links — all three stay correct automatically as the project changes.

Remember

  • {% extends %} must be the first tag; an unoverridden block falls back to the parent; {{ block.super }} adds to it.
  • Autoescaping is on by default — |safe/mark_safe() only for a KNOWN trusted transformation's output, never raw user input.
  • Custom tags/filters live in templatetags/, need {% load name %} in every template that uses them.
  • Never hard-code what {% csrf_token %}/{% static %}/{% url %} compute — all three stay correct as the project changes.

Pro tip

  • {% include %} renders independently of extends/block — it shares context by default; add `only` to scope it.
  • APP_DIRS searches DIRS first, then each app's templates/ in INSTALLED_APPS order — first match wins, so filenames can collide.

Next up

  • Forms
  • Django Models
  • Django Field Types

Sources

  • Built-in template tags — docs.djangoproject.com/en/5.2/ref/templates/builtins/
  • Custom template tags — docs.djangoproject.com/en/5.2/howto/custom-template-tags/
  • Every claim checked against Django 5.2 LTS docs · checked 2026-08-21

Spotted a mistake on this page? Tell us.