Template Inheritance Basics

Use template inheritance to organize pages — Python web development tutorial, lesson 13. Learn how to create a base template and extend it to keep your code DRY and consistent across pages.

Focus: use template inheritance to organize pages

Sponsored

You've built a few pages already — a landing page here, a dashboard there — and you've started to notice the pain: copy-pasting the same nav bar, footer, and <head> block into every template. Change one link in the footer and you're hunting through five files. That's the problem this lesson solves. Template inheritance lets you define a single base layout and have every other page extend it, so your templates stay DRY, consistent, and a joy to maintain. By the end of this lesson, you'll be able to use template inheritance to organize pages in your Flask (or Jinja2-based) projects, and you'll have a clean pattern for every future page you add.

The problem this lesson solves

As your app grows from a single route to several — say, a home page, a profile page, and an admin panel — you'll hit a wall. Without inheritance, every template repeats the same boilerplate:

<!doctype html>
<html>
<head>
    <link rel="stylesheet" href="/static/style.css">
    <script src="/static/app.js"></script>
</head>
<body>
    <nav><!-- same links everywhere --></nav>
    <!-- page-specific content -->
    <footer><!-- same footer everywhere --></footer>
</body>
</html>

Why it hurts:

  • Duplication — the nav bar exists in five files. A single typo or style tweak means editing all of them.
  • Inconsistency — you forget to update one template and suddenly the footer is out of date on that page.
  • Friction — every new page requires copying a 30-line skeleton just to write 10 lines of unique content.

This is the classic DRY (Don't Repeat Yourself) problem applied to HTML. Template inheritance is the solution, and it's built into Jinja2, the default templating engine for Flask and many other Python web frameworks.

Core concept / mental model

Think of template inheritance like a blueprint for a house. The base template (the blueprint) defines the structural walls, the electrical wiring, and the plumbing — everything that's the same in every room. Each room (a child template) then adds its own furniture and decor inside the designated spaces.

In Jinja2, the base template defines blocks — named sections that child templates can override. The most common block is content, but you can have as many blocks as you need: head, styles, scripts, sidebar, footer, and so on.

Key terms:

  • Base template — the parent layout with all common structure and blocks.
  • Child template — a page that extends the base and fills in blocks.
  • {% extends %} — the tag that tells Jinja2 which base template to use.
  • {% block name %}...{% endblock %} — defines a section that can be overridden.

A quick visual:

base.html
+--------------------------------------+
|       <!doctype html>               |
|       <head> ... </head>            |
|       <nav> ... </nav>              |
|                                      |
|   {% block content %}               |
|       (default content, optional)   |
|   {% endblock %}                     |
|                                      |
|       <footer> ... </footer>         |
+--------------------------------------+
            ^
            | extends
            |
home.html  +--------------------------------------+
           | {% extends "base.html" %}           |
           | {% block content %}                 |
           |   <h1>Welcome</h1>                  |
           |   <p>...</p>                        |
           | {% endblock %}                       |
           +--------------------------------------+

The child template only needs to write the content block; everything else comes from the parent. If you need to tweak the <head> for a specific page, you can override the head block too.

How it works step by step

Here's the play-by-play for setting up template inheritance in a Flask app:

  1. Create a base template (usually templates/base.html).
  2. Structure it with your common HTML — <head>, nav, footer — and define blocks at the places that will change.
  3. Create child templates for each page.
  4. In each child, add {% extends "base.html" %} at the top.
  5. Override the blocks you need (at minimum, content).
  6. Render as usual with render_template().

Key point: The {% extends %} tag must be the first tag in the child template, before any output. If you put a blank line or text before it, Jinja2 will raise an error or silently ignore the inheritance.

Hands-on walkthrough

Let's build a minimal Flask app with three pages: home, about, and contact. We'll use template inheritance to keep them consistent.

First, set up the project structure:

project/
├── app.py
└── templates/
    ├── base.html
    ├── home.html
    ├── about.html
    └── contact.html

Now create the base template — notice the blocks for title, head, and content:

<!-- templates/base.html -->
<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{% block title %}My Site{% endblock %}</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
    {% block head %}{% endblock %}
</head>
<body>
    <nav>
        <a href="{{ url_for('home') }}">Home</a> |
        <a href="{{ url_for('about') }}">About</a> |
        <a href="{{ url_for('contact') }}">Contact</a>
    </nav>
    <main>
        {% block content %}{% endblock %}
    </main>
    <footer>
        <p>© {{ current_year }} My Site</p>
    </footer>
</body>
</html>

Now create a child template for the home page:

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

{% block title %}Home{% endblock %}

{% block content %}
<h1>Welcome to My Site</h1>
<p>This is the home page. All pages share the same base layout.</p>
{% endblock %}

Do the same for about.html and contact.html — only the content block changes. Finally, wire up the Flask routes in app.py:

# app.py
from flask import Flask, render_template
from datetime import datetime

app = Flask(__name__)

# Make current_year available in all templates
@app.context_processor
def inject_year():
    return {'current_year': datetime.now().year}

@app.route('/')
def home():
    return render_template('home.html')

@app.route('/about')
def about():
    return render_template('about.html')

@app.route('/contact')
def contact():
    return render_template('contact.html')

if __name__ == '__main__':
    app.run(debug=True)

Run python app.py and visit /, /about, and /contact. Each page should have the same nav and footer, and the title changes per page. Notice how you didn't repeat the <head> or <nav> anywhere except base.html.

Pro tip: The {% block title %} pattern is powerful — you can even nest it like {% block title %}{{ super() }} — Home{% endblock %} to append to the base title.

Compare options / when to choose what

Jinja2 offers several ways to compose templates. Here's a quick comparison:

Approach Use case Pros Cons
Template inheritance (extends) Shared layout across many pages (nav, footer, head) DRY, easy to maintain, natural for page-level design Requires planning blocks upfront
Includes ({% include %}) Reusable snippets like a comment box or a card Simple, no hierarchy Can lead to duplication if overused
Macros ({% macro %}) Reusable render functions for small UI pieces Powerful, callable with params Overkill for layout-level reuse

Choose inheritance when the same overall page structure (header, nav, footer) repeats across pages — which is the majority of typical web apps. Use includes for components that appear within different pages in different contexts, like a sidebar or a widget. Use macros for generating small, repetitive HTML patterns (e.g., form fields, badges) with different values.

Variations: Some frameworks use different terms — Django calls inheritance {% extends %} too, but with a different template syntax. Flask's Jinja2 is the most direct approach in the Python world; you'll find the same concepts in tools like Pug (for Node) or Twig (for PHP) if you ever switch ecosystems.

Troubleshooting & edge cases

1. TemplateNotFound: base.html — You forgot to extend the template name correctly. Make sure base.html is in the templates/ folder and you're extending it with the exact filename, e.g., {% extends "base.html" %}. If you have subfolders, use the relative path like {% extends "layouts/base.html" %}.

2. Blank page or unexpected output — The {% extends %} tag must be the first thing in the child template. Even a single whitespace character before it can break inheritance. Put it at the very top, with no blank lines before it.

3. Block content not showing — You defined a block in the base but forgot to override it in the child. If the base block is empty, the child shows nothing. Define a default inside the block in the base, or ensure you override it.

4. Duplicate blocks — Jinja2 will raise a TemplateAssertionError if you define the same block name twice in the same template. Use unique names per block.

5. super() not working — You want to extend the parent's block content. Use {{ super() }} inside the child block. If it doesn't work, check that you actually have content in the parent's block — super() pulls from the block definition in the immediate parent, not the grandparent unless you chain it.

Edge case: nested blocks — Blocks can be nested inside other blocks. The child can override the outer block, and inside that, it can still override the inner block. Just keep track of which block you're targeting.

What you learned & what's next

Let's recap what you accomplished:

  • You understand why template inheritance matters — it removes duplication, keeps your UI consistent, and makes maintenance a breeze.
  • You built a mental model: base layout + child pages = clean architecture.
  • You walked through the steps to create a base template with blocks and extend it in child templates.
  • You completed a hands-on exercise with three pages sharing one layout.
  • You learned how to choose between inheritance, includes, and macros for different scenarios.
  • You debugged common inheritance pitfalls like misplaced extends tags and empty blocks.

You're now equipped to use template inheritance to organize pages in any Flask app. But inheritance is just the first tool in your template toolkit. In the next lesson, you'll explore template partials and includes — reusing components like headers and cards without repeating code. That will let you build even more modular and maintainable templates.

Keep practicing, and soon you'll design templates like a seasoned architect.

Practice recap

Now it's your turn: extend the mini site we built by adding a new page — say, a profile.html — that extends base.html and overrides the content block with a simple heading and paragraph. Change the title in the base to include your site name, then view the page in your browser to confirm the header and footer match the other pages. Finally, add a sidebar block to the base and override it in one page to see how additional blocks work.

Common mistakes

  • Putting any text or blank lines before {% extends %} — this breaks the inheritance and can cause a blank page or a syntax error.
  • Overriding a block with the same name twice in one template — Jinja2 raises a TemplateAssertionError; use unique block names.
  • Expecting child content to appear without overriding the block — some blocks like content are empty by default; always add the override.
  • Forgetting to include {{ super() }} inside a block when you want to keep the parent's default — without it, the base content is lost.

Variations

  1. Django's template inheritance — uses the same {% extends %} concept but with different syntax (e.g., {% block %} vs {% block %} is similar, but Django auto-escapes differently).
  2. Jinja2 macros — instead of inheritance, you can define reusable UI snippets as functions and call them with parameters.
  3. Component-based approaches — frameworks like Vue or React use a different paradigm, but you can mimic it with includes and macros in Jinja2.

Real-world use cases

  • A SaaS dashboard with 20+ pages, all sharing the same sidebar, top bar, and footer — inheritance keeps the layout consistent and changes propagate instantly.
  • An e-commerce site where product pages, category pages, and the checkout all need the same header/nav — extend a base template and override only the main content area.
  • A Flask-based blog where you have a single base layout for articles, archive, and about pages, plus a separate admin base with its own blocks for dashboard widgets.

Key takeaways

  • Template inheritance removes duplication by defining a base layout with blocks that child templates override.
  • Use {% extends 'base.html' %} as the first line in every child template to reuse the parent layout.
  • Design blocks carefully: start with title, head, and content, and add more as your pages diverge.
  • Leverage {{ super() }} to preserve parent block content while adding your own.
  • Prefer inheritance for whole-page layout; use includes and macros for smaller, reusable components.
  • Put {% extends %} at the very top of the file — no whitespace before it — to avoid silent errors.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.