Template Inheritance & Includes
The books/list.html template from the last lesson works on its own, but a
real Bookshelf app has more than one page — a book list, a book detail page, a shelf page,
an "add book" form. If every one of those templates started with its own full copy of the
<html> shell, the nav bar, and the footer, changing the nav bar even
slightly would mean editing every single template file. Jinja2 solves this with
template inheritance and includes — two related but
distinct tools for sharing markup across templates.
The Problem Inheritance Solves
Without inheritance, adding a new page to Bookshelf means copy-pasting the header, navigation links, and footer from an existing template into the new one. That's fine for one page. By the tenth page, a single wording change to the nav bar becomes a ten-file find-and-replace, and it's only a matter of time before one of those copies drifts out of sync with the rest. Inheritance flips the relationship: one template defines the shared shell once, and every page template fills in only the part that's actually unique to it.
A Base Template With A Content Block
A base template looks like a normal HTML page, except that one region is marked with
{% block content %}{% endblock %} — a named placeholder that child
templates are allowed to fill in:
{# templates/base.html #}
<!DOCTYPE html>
<html>
<head>
<title>Bookshelf</title>
</head>
<body>
<nav>
<a href="{{ url_for('book_list') }}">My Books</a>
<a href="{{ url_for('shelf_list') }}">Shelves</a>
</nav>
<main>
{% block content %}{% endblock %}
</main>
</body>
</html>
Extending base.html From A Child Template
A child template opens with {% extends 'base.html' %} and then defines
its own {% block content %} ... {% endblock %} — Jinja2 takes the
whole base template, and drops this content into the matching block, leaving everything
else from base.html untouched:
{# templates/books/list.html #}
{% extends 'base.html' %}
{% block content %}
<h1>{{ shelf_name }}</h1>
<ul>
{% for book in books %}
<li>{{ book.title }} by {{ book.author }}</li>
{% endfor %}
</ul>
{% endblock %}
Rendering books/list.html now produces the full page — <html>,
the nav bar with its two links, and this template's book list dropped into
<main> — even though this file never wrote a single one of those
surrounding tags itself.
Multiple Named Blocks In One Base Template
A base template isn't limited to one block. Adding a second block for the page
<title> lets each child template set its own browser tab title, while
still sharing everything else:
{# templates/base.html #}
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Bookshelf{% endblock %}</title>
</head>
<body>
<nav>
<a href="{{ url_for('book_list') }}">My Books</a>
<a href="{{ url_for('shelf_list') }}">Shelves</a>
</nav>
<main>
{% block content %}{% endblock %}
</main>
</body>
</html>
{# templates/books/list.html #}
{% extends 'base.html' %}
{% block title %}My Books - Bookshelf{% endblock %}
{% block content %}
<h1>{{ shelf_name }}</h1>
<ul>
{% for book in books %}
<li>{{ book.title }} by {{ book.author }}</li>
{% endfor %}
</ul>
{% endblock %}
A child template can override as few or as many blocks as it needs — leave
{% block title %} out entirely, and the base template's default
"Bookshelf" is used instead, since Jinja2 falls back to whatever content the
base template already put between that block's tags.
Reusable Fragments With include
extends is for a whole page's shared shell. {% include %}
solves a different, smaller problem: pulling in one reusable fragment —
not a full page, just a piece of markup used in more than one place. A single book's card
is a good example — the same layout is needed on the book list page and again on a shelf's
detail page:
{# templates/partials/book_card.html #}
<div class="book-card">
<h3>{{ book.title }}</h3>
<p>{{ book.author }} — {{ book.pages }} pages</p>
<p>Status: {{ book.status }}</p>
</div>
{# templates/books/list.html — reused inside a loop #}
{% extends 'base.html' %}
{% block content %}
{% for book in books %}
{% include 'partials/book_card.html' %}
{% endfor %}
{% endblock %}
The same partials/book_card.html file can be dropped into a shelf detail
template too, with no changes at all — write the card's markup once, and every page that
needs a book card includes it.
extends vs. include: Choosing The Right Tool
| Tag | Use it for |
|---|---|
{% extends %} | Whole-page inheritance — one base layout, one child template fills in the unique regions |
{% include %} | A smaller reusable fragment dropped into place wherever it's needed, often inside a loop |
A template can only extends one parent, and it must be the first tag in the
file. include has no such restriction — a single template can include several
different fragments, anywhere in its body, as many times as needed.
Implicit Context In Included Templates
Notice that partials/book_card.html above references book
without that variable ever being explicitly passed to the include tag. In
Jinja2, an included template automatically sees every variable already visible in the
template that includes it — the book loop variable from the surrounding
{% for book in books %} block is simply in scope. This is a genuinely
convenient Jinja2-specific detail: Django's template language, by contrast, requires an
explicit {% include ... with book=book %} to pass context into an
included template. In Jinja2, it's automatic, and you only need to reach for a manual
approach in the rarer case where you want to isolate an included template from the
surrounding context on purpose.