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>
Note: Every template on this page is correct, working Jinja2, but this website's own project doesn't run a Flask server, so none of it renders live here — read each one alongside the explanation, then try it in your own project.

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

TagUse 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.

Next up: Base templates and includes handle shared structure — Custom Filters & Context Processors covers making your own reusable pieces of template logic: custom filters for app-specific formatting, and context processors for data every template needs without passing it explicitly.