Skip to main content

πŸ›οΈ Template Inheritance and Includes

Copying the same navbar and footer into every page is a maintenance nightmare β€” change one link and you're editing twenty files. Jinja2's inheritance lets you define your site's skeleton once and let every page fill in only what's unique. This is the single most important technique for a maintainable Flask front end.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Build a base template with {% block %} regions and extend it with {% extends %}
  • Override blocks in child templates and provide default block content
  • Use {{ super() }} to extend a parent block rather than replace it
  • Compose UI from reusable partials with {% include %}
  • Design a multi-level inheritance hierarchy for a real application

Estimated Time: 30–40 minutes  β€’  Difficulty: Intermediate

Hands-on: Refactor two duplicated pages into a shared base template with an included component.

In This Lesson

Why Inheritance?

Every page on a typical site shares the same shell: the same <head>, the same navigation bar, the same footer. Only the middle β€” the page's actual content β€” changes. Template inheritance captures that shell in one base (or parent) template, and each page extends it, overriding only the parts it needs to.

πŸ’‘ A useful analogy: The base template is a house's frame β€” foundation, walls, roof, plumbing β€” built once. Each room (child template) keeps that frame and just decides its own furniture and paint. Nobody rebuilds the roof to redecorate a bedroom.

This is the DRY principle β€” Don't Repeat Yourself β€” applied to markup. Change the navbar in the base template and every page updates at once.

The Base Template & Blocks

A base template is ordinary HTML with {% block name %}…{% endblock %} markers carving out the regions children may override. Conventionally it lives at templates/base.html:

<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}My Site{% endblock %}</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
    {% block extra_css %}{% endblock %}
</head>
<body>
    <header>
        <nav>
            <a href="{{ url_for('home') }}">Home</a>
            <a href="{{ url_for('about') }}">About</a>
        </nav>
    </header>

    <main>
        {% block content %}{% endblock %}
    </main>

    <footer>
        <p>&copy; 2026 My Site</p>
    </footer>

    {% block extra_js %}{% endblock %}
</body>
</html>

This base defines four override points: title, extra_css, content, and extra_js. The content block is empty (children must fill it); the others carry sensible defaults.

πŸ“– Key Terms

Block: a named, overridable region defined with {% block %}.

Extends: the directive a child uses to inherit from a base template.

Override: redefining a block in a child to replace the parent's version.

Child Templates

A child template's first line is {% extends "base.html" %}. After that, it only defines the blocks it wants to change β€” everything else comes from the parent automatically:

<!-- templates/home.html -->
{% extends "base.html" %}

{% block title %}Home β€” My Site{% endblock %}

{% block content %}
    <h1>Welcome!</h1>
    <p>This is the home page.</p>
{% endblock %}
<!-- templates/about.html -->
{% extends "base.html" %}

{% block title %}About β€” My Site{% endblock %}

{% block content %}
    <h1>About Us</h1>
    <p>A small team building useful things.</p>
{% endblock %}

Both pages render with identical navigation and footer, yet each supplies its own title and body. That's the whole payoff of inheritance in two files.

Template inheritance hierarchy A single base template is extended by three child templates: home, about, and contact. base.html shell + blocks home.html about.html contact.html extends
Figure 1 β€” One base template, many children. Each child overrides only the blocks it cares about and inherits the rest.

Defaults & super()

Default block content

A block in the base can carry default content that children get for free unless they override it. A sidebar is a classic example:

{% block sidebar %}
    <aside class="default-sidebar">
        <h3>Quick Links</h3>
        <a href="{{ url_for('docs') }}">Docs</a>
    </aside>
{% endblock %}

Extending, not replacing, with super()

Sometimes you want to add to the parent's block rather than throw it away. The {{ super() }} call inserts the parent's block content, so you keep it and append your own. This is especially handy for extra_css / extra_js:

<!-- In a child template -->
{% block extra_js %}
    {{ super() }}  {# keep whatever the parent loaded #}
    <script src="{{ url_for('static', filename='js/charts.js') }}"></script>
{% endblock %}

βœ… Rule of thumb

Override a block when the child needs something completely different. Call {{ super() }} when the child needs the parent's content plus a little more.

Includes: Reusable Partials

Inheritance handles the page-wide skeleton. For smaller fragments reused across many pages β€” an alert box, a product card, a flash-message strip β€” reach for {% include %}. It pastes another template's rendered output right where you call it.

<!-- templates/partials/alert.html -->
<div class="alert alert-{{ category }}">
    {% if title %}<strong>{{ title }}</strong>{% endif %}
    <p>{{ message }}</p>
</div>

By default an included template can see the surrounding context's variables. Include it wherever needed:

{% extends "base.html" %}

{% block content %}
    <h1>Dashboard</h1>
    {% include "partials/alert.html" %}
    <p>Your latest activity…</p>
{% endblock %}

πŸ’‘ include vs. import

Use {% include %} to drop in a chunk of ready-made HTML. Use {% import %} / {% from … import … %} to pull in macros (template functions) that you then call with arguments. Rule of thumb: include markup, import macros.

A common real-world partial is the flash-messages strip that shows feedback after a redirect:

<!-- templates/partials/flashes.html -->
{% with messages = get_flashed_messages(with_categories=true) %}
    {% if messages %}
        {% for category, message in messages %}
            <div class="alert alert-{{ category }}">{{ message }}</div>
        {% endfor %}
    {% endif %}
{% endwith %}

Multi-Level Hierarchies

Inheritance can go more than one level deep. A large app often has a global base.html, then section bases that extend it (a store layout, an admin layout), and finally the leaf pages. Each layer adds its own structure:

<!-- templates/admin/base.html -->
{% extends "base.html" %}

{% block content %}
    <div class="admin-layout">
        <nav class="admin-sidebar">
            <a href="{{ url_for('admin.users') }}">Users</a>
            <a href="{{ url_for('admin.orders') }}">Orders</a>
        </nav>
        <section class="admin-main">
            {% block admin_content %}{% endblock %}
        </section>
    </div>
{% endblock %}
<!-- templates/admin/users.html -->
{% extends "admin/base.html" %}

{% block admin_content %}
    <h1>User Management</h1>
    {# table of users… #}
{% endblock %}
flowchart TD A["base.html"] --> B["store/base.html"] A --> C["admin/base.html"] B --> D["product_list.html"] B --> E["product_detail.html"] C --> F["admin/users.html"] C --> G["admin/orders.html"] H["partials/product_card.html"] -.include.-> D I["partials/flashes.html"] -.include.-> F

Here admin/users.html only fills admin_content. It inherits the sidebar from admin/base.html, which in turn inherits the site navbar and footer from the global base.html. Three layers, zero duplication.

Best Practices

βœ… Do

  • Keep the base template focused on structure shared by all pages.
  • Give blocks descriptive names (content, sidebar, extra_js).
  • Store partials in a partials/ or components/ folder to keep templates tidy.
  • Reach for a section base (e.g. admin/base.html) once several pages share extra chrome.

⚠️ Don't

  • Put page-specific content in the base template "just for now" β€” it leaks onto every page.
  • Nest inheritance more than three or four levels deep; it becomes hard to trace.
  • Forget that {% extends %} must be the very first tag in a child template.

Hands-on Exercise

πŸ‹οΈ Refactor Two Duplicated Pages

Objective: Turn two standalone pages that repeat their shell into a base template plus an included component.

Instructions:

  1. You start with home.html and about.html, each with its own full <html>, navbar, and footer.
  2. Create base.html holding the shared shell with a {% block title %} and a {% block content %}.
  3. Rewrite both pages to {% extends "base.html" %} and fill only title and content.
  4. Move the footer text into partials/footer.html and pull it into the base with {% include %}.
  5. Confirm both pages still render correctly, then change one navbar link in base.html and verify it updates on both.
πŸ’‘ Hint

The child's very first line must be {% extends "base.html" %} β€” no HTML before it. Anything outside a {% block %} in a child template is ignored, so make sure your page content lives inside {% block content %}.

βœ… Sample solution
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="en">
<head><title>{% block title %}Site{% endblock %}</title></head>
<body>
    <nav><a href="{{ url_for('home') }}">Home</a></nav>
    <main>{% block content %}{% endblock %}</main>
    {% include "partials/footer.html" %}
</body>
</html>

<!-- templates/home.html -->
{% extends "base.html" %}
{% block title %}Home{% endblock %}
{% block content %}<h1>Welcome</h1>{% endblock %}

🎯 Quick Quiz

Question 1: Which directive makes a child template inherit from a base template?

Question 2: Inside a child block, what does {{ super() }} do?

Question 3: You have an alert box reused on many pages. Which tool fits best?

Summary & Quiz

πŸŽ‰ Key Takeaways

  • Inheritance puts the shared shell in a base template; children {% extends %} it and override {% block %}s.
  • Blocks can carry default content; {{ super() }} keeps the parent's content while adding more.
  • {% include %} reuses small fragments; {% import %} pulls in macros.
  • Multi-level hierarchies (global β†’ section β†’ page) scale to large apps with zero duplication.
  • The DRY payoff: fix the navbar once, and every page updates.

πŸ“š Further Reading

πŸš€ What's Next?

Your templates are now organized and DRY. Next we'll make them interactive β€” handling user input safely with Flask-WTF, complete with validation and CSRF protection.

πŸŽ‰ Nicely refactored!

One shell, many pages. Let's collect some data from your users next.