Template Inheritance
Reviewed & published by Brayan K
Template inheritance in Django lets you define a single base template with the shared layout of your site — the header, footer, and navigation — and then have child templates extend it and fill in only the parts that change, so you never repeat your HTML skeleton.
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 base template with named blocks, write child pages that extend it, extend a parent block with block.super, and pull in reusable partials with the include tag.
What You'll Learn in This Lesson
- Why template inheritance keeps your HTML DRY
- Creating a base.html with named block tags
- Extending a base template from a child page
- Overriding blocks to customise each page
- Using block.super to keep parent content
- Reusing partials with the include tag and variables
1 Creating a Base Template with Blocks
A base template holds the structure every page on your site shares — the doctype, the head, the navigation bar, and the footer. Inside it you mark the regions that child pages are allowed to replace using {% block %} tags. Every block has a name and ends with {% endblock %}.
A typical base defines at least two blocks: a {% block title %} in the head for the page title, and a {% block content %} in the body where each page drops its main markup.
Here is a real base.html file:
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{% block title %}My Site{% endblock %}</title>
</head>
<body>
<header>
<nav>Home | Blog | About</nav>
</header>
<main>
{% block content %}
<!-- Child pages fill this in -->
{% endblock %}
</main>
<footer>© 2026 My Site</footer>
</body>
</html>Anything outside a block — the header, nav, and footer here — is fixed and appears on every page. Anything inside a block is the default; a child can override it or leave it as-is.
# Django renders templates on the server. To SEE how a base "skeleton"
# with a fillable region works, we simulate it in plain Python: the base
# is a string with a {slot} placeholder, just like a block.
base = """<html>
<head><title>{title}</title></head>
<body>
<nav>Home | Blog | About</nav>
{content}
<footer>(c) 2026 My Site</footer>
</body>
</html>"""
# Default values, like the defaults inside a {% block %}
page = base.format(
title="My Site",
content="<p>Welcome to the home page.</p>",
)
print(page)
# Notice the nav and footer are shared by every page that uses 'base'.
# ✅ Expected output:
# <html>
# <head><title>My Site</title></head>
# <body>
# <nav>Home | Blog | About</nav>
# <p>Welcome to the home page.</p>
# <footer>(c) 2026 My Site</footer>
# </body>
# </html>2 Extending the Base and Overriding Blocks
A child template starts with the {% extends "base.html" %} tag — and it must be the very first line of the file. After that, the child only needs to redefine the blocks it wants to change. Any block it leaves out keeps the default from the base.
A child page, home.html:
<!-- templates/home.html -->
{% extends "base.html" %}
{% block title %}Home — My Site{% endblock %}
{% block content %}
<h1>Welcome!</h1>
<p>This is the home page content.</p>
{% endblock %}Sometimes you want to add to the parent block instead of replacing it. Calling {{ block.super }} inside a child block outputs whatever the parent block contained, so you can keep it and extend it:
<!-- templates/about.html -->
{% extends "base.html" %}
{% block title %}About — {{ block.super }}{% endblock %}
{% block content %}
<h1>About Us</h1>
<p>We build great software.</p>
{% endblock %}Here the title becomes About — My Site: the child's text plus the parent's default, pulled in by {{ block.super }}.
# Simulate a CHILD overriding a block, and block.super keeping the parent.
# The base block "title" has a default; the child adds to it.
parent_title = "My Site" # default inside {% block title %}
def render_title(child_text=None, use_super=False):
if child_text is None:
return parent_title # block left untouched
if use_super:
# {{ block.super }} = parent's content appended after child's text
return child_text + " — " + parent_title
return child_text # full override
print(render_title()) # untouched -> My Site
print(render_title("Home")) # full override -> Home
print(render_title("About", use_super=True)) # with block.super
# ✅ Expected output:
# My Site
# Home
# About — My Site3 Reusing Partials with the include Tag
Inheritance shares the outer skeleton; the {% include %} tag shares inner snippets. A partial is a small standalone template — a card, an alert, a navbar — that you drop into a page wherever you need it. Unlike extends, you can include many partials, and anywhere in the file.
<!-- templates/partials/card.html -->
<div class="card">
<h3>{{ title }}</h3>
<p>{{ body }}</p>
</div>You include it and pass variables with the with keyword. Each value becomes a variable inside the partial:
<!-- templates/dashboard.html -->
{% extends "base.html" %}
{% block content %}
<h1>Dashboard</h1>
{% include "partials/card.html" with title="Sales" body="Up 12%" %}
{% include "partials/card.html" with title="Users" body="1,204 active" %}
{% endblock %}The same card.html is rendered twice with different data — write the markup once, reuse it everywhere.
# Simulate {% include "card.html" with title=... body=... %}.
# A partial is a reusable template fragment we fill with passed-in variables.
def render_card(title, body):
"""Stands in for partials/card.html receiving 'with' variables."""
return f'<div class="card"><h3>{title}</h3><p>{body}</p></div>'
# Including the same partial twice with different data:
cards = [
render_card("Sales", "Up 12%"),
render_card("Users", "1,204 active"),
]
for card in cards:
print(card)
# One template fragment, reused with different context each time.
# ✅ Expected output:
# <div class="card"><h3>Sales</h3><p>Up 12%</p></div>
# <div class="card"><h3>Users</h3><p>1,204 active</p></div>🎯 Your Turn: Fill In the Block
A child page should override the content region. Replace ___ with the block name so the renderer fills the content slot.
# 🎯 YOUR TURN: replace ___ with the correct block name: "content"
# In Django this mirrors: {% block content %}...{% endblock %}
base = "<body><nav>Menu</nav>[{slot}]<footer>Foot</footer></body>"
blocks = {"title": "My Site"}
# Tell the child which block to fill:
block_to_fill = "___"
blocks[block_to_fill] = "<h1>Hello World</h1>"
print(base.replace("{slot}", blocks.get("content", "EMPTY")))
# EXPECTED OUTPUT:
# <body><nav>Menu</nav>[<h1>Hello World</h1>]<footer>Foot</footer></body>Common Errors (And How to Fix Them)
❌ TemplateSyntaxError: extends must be first
The {% extends %} tag has text, comments, or blank lines before it.
✅ Fix: make {% extends "base.html" %} the very first line of the file, with nothing above it.
❌ Invalid block tag: expected endblock
You opened a {% block %} but never closed it, or the names don't match up.
✅ Fix: close every block with {% endblock %} — optionally name it, e.g. {% endblock content %}.
❌ My content just doesn't show up
In a child template, anything outside a block is silently ignored — only block content renders.
✅ Fix: put every child element inside a {% block %} that the base actually defines.
📋 Quick Reference
| Tag | What It Does |
|---|---|
| {% extends "base.html" %} | Declare this template a child of base.html (must be first line) |
| {% block name %} | Open an overridable region named name |
| {% endblock %} | Close the most recently opened block |
| {% include "p.html" %} | Render another template inline at this spot |
| {{ block.super }} | Output the parent block's content inside a child block |
🏆 Mini Challenge: Render a Page from a Base
Build a tiny renderer that takes a base skeleton plus a child's block overrides and produces the final page — exactly what Django's inheritance does for you.
# 🏆 Mini Challenge: render a child page against a base template.
# Mirrors {% extends %} + {% block %} overrides.
base = {
"title": "My Site", # default block content
"content": "<p>Default content.</p>",
}
def render_page(base, overrides):
# A child only redefines some blocks; the rest keep base defaults.
blocks = {**base, **overrides}
return (
"<html><head><title>" + blocks["title"] + "</title></head>"
"<body><nav>Home | Blog</nav>" + blocks["content"] +
"<footer>(c) 2026</footer></body></html>"
)
child = {
"title": "About — My Site", # like using {{ block.super }}
"content": "<h1>About Us</h1>",
}
print(render_page(base, child))
# EXPECTED: nav + footer come from base; title & content from the child.❓ Frequently Asked Questions
Lesson complete — your templates are DRY!
You can now build a base template with named blocks, extend it from child pages, override or augment blocks with block.super, and pull in reusable partials with the include tag.
Practice quiz
What does template inheritance let you do?
- Run Python code directly inside templates
- Define a base layout once and override only the parts that change
- Automatically minify HTML output
- Connect templates to the database
Answer: Define a base layout once and override only the parts that change. Inheritance lets a child template extend a base and override named blocks, keeping HTML DRY.
Which tag makes a template a child of base.html?
- {% extends 'base.html' %}
- {% include 'base.html' %}
- {% block base.html %}
- {% import 'base.html' %}
Answer: {% extends 'base.html' %}. {% extends 'base.html' %} declares the template inherits from base.html.
Where must the extends tag appear in a child template?
- Anywhere inside the body block
- Right before the first block tag
- After the doctype declaration
- As the very first line of the file
Answer: As the very first line of the file. The {% extends %} tag must be the first thing in the file or Django raises a TemplateSyntaxError.
What defines an overridable region in a base template?
- {% region %} ... {% endregion %}
- {% slot %} ... {% endslot %}
- {% block name %} ... {% endblock %}
- {{ block name }}
Answer: {% block name %} ... {% endblock %}. A {% block name %} ... {% endblock %} marks a region a child template can override.
What does {{ block.super }} output inside a child block?
- The parent block's content, so the child can extend rather than replace it
- The name of the parent template
- Nothing — it is invalid syntax
- The entire base template
Answer: The parent block's content, so the child can extend rather than replace it. {{ block.super }} emits the parent block's content, letting the child add to it.
How many parent templates can a single template extend?
- Up to three
- Exactly one
- Any number
- None — extends is optional decoration
Answer: Exactly one. A template can extend exactly one base; to share more, nest your base templates.
Which tag renders another template's contents inline at that spot?
- {% extends %}
- {% block %}
- {% super %}
- {% include %}
Answer: {% include %}. {% include 'partial.html' %} drops another template's content inline where it appears.
In a child template, what happens to content placed outside any block?
- It replaces the base's footer
- It renders at the top of the page
- It is silently ignored and does not render
- It causes a TemplateSyntaxError
Answer: It is silently ignored and does not render. Only content inside blocks the base defines renders; markup outside a block is ignored.
How do you pass variables into an included partial?
- With the 'with' keyword, e.g. {% include 'card.html' with title='Sales' %}
- By listing them after a colon
- Partials cannot receive variables
- By calling {{ block.super }}
Answer: With the 'with' keyword, e.g. {% include 'card.html' with title='Sales' %}. The 'with' keyword passes named values that become variables inside the included partial.
What is the key difference between extends and include?
- extends works only in the body; include works only in the head
- include can only be used once per file
- They are interchangeable aliases
- extends inherits a base's whole structure with overridable blocks; include drops a snippet inline
Answer: extends inherits a base's whole structure with overridable blocks; include drops a snippet inline. extends pulls in a base layout and overrides blocks; include renders a reusable snippet at its location.
Continue this course
- Previous: Generic Views
- Next: URL Namespacing — Organise URLs with app namespaces and reverse them by name
- Quick reference: Django cheat sheet › Templates
Frequently asked questions
What is template inheritance in Django?
Template inheritance is a feature of the Django template language that lets you build a single base template containing the shared skeleton of your site — the doctype, the header, the navigation, and the footer — and then have other templates extend that base and override only the small regions that change from page to page. Those overridable regions are called blocks. Instead of copying the whole HTML structure into every page, each child template fills in the blocks it cares about, which keeps your markup DRY and consistent.
What is the difference between extends and include?
The extends tag declares that the current template is a child of a base template; it pulls in the base's entire structure and lets the child override named blocks. A template can only extend one parent, and the extends tag must be the very first thing in the file. The include tag is different: it renders another template's contents inline at the exact spot where you place it, like dropping a reusable snippet — a navbar or a card — into the middle of a page. You can include as many partials as you like, and you can pass variables into them.
What does block.super do?
Inside a child block, block.super outputs whatever the parent block contained. This is useful when you want to add to the parent's content rather than replace it entirely. For example, if the base template's title block contains the site name, a child page can write its own page title and then append the site name by calling block.super, so the parent content is preserved and extended rather than discarded.
Why must the extends tag be the first line of a child template?
Django needs to know immediately that a template is inheriting from a parent so it can load the parent's structure before processing anything else. If any text or tags appear before extends, Django raises a TemplateSyntaxError. Even blank lines or stray characters before the extends tag will break it, so always make the extends tag the very first line of the file.