13.3 Overriding Blocks in Child Templates
Right, so you’ve got a base template. It’s a beautiful, well-structured piece of art, full of {% block %} tags like little marked-up landing zones. But you don’t want what the base template serves by default. You want your content in its structure. This is where you stop merely inheriting and start overriding. It’s the core of Jinja’s power, and frankly, it’s where most people first get themselves into a proper muddle. Let’s untangle it.
The concept is simple: in your child template, you redefine a block from the base. When Jinja renders, it uses the base’s structure but says, “Ah, the child has an opinion on this particular block! I’ll use that instead.” It’s like politely asking the chef to hold the onions.
Here’s our hopelessly simplistic base template, base.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}My Amazing Site{% endblock %}</title>
</head>
<body>
<header>{% block header %}A Default Header{% endblock %}</header>
<main>{% block content %}<p>Default content. Thrilling.</p>{% endblock %}</main>
<footer>{% block footer %}{% endblock %}</footer>
</body>
</html>
Now, in child.html, you extend the base and target one of those blocks:
{% extends "base.html" %}
{% block content %}
<h1>Welcome to MY page</h1>
<p>This is my own, far more exciting content, completely replacing the default drivel.</p>
{% endblock %}
Notice you only defined the content block. Jinja will happily render the rest of the page using the defaults from base.html for title, header, and footer. Clean, simple, effective.
The Super-Important {{ super() }} Call
Now, what if you don’t want to replace the base block’s content, but add to it? This is the most common “aha!” moment for new Jinja users. You use {{ super() }}. This function jinja-whisperes, “Hey, go get the content that was originally in this block from the parent template and plop it right here.”
Let’s say you want to keep the base’s title but add a site section to it. You’d override the title block, but use super() to include the original.
{% extends "base.html" %}
{% block title %}
{{ super() }} - User Profile
{% endblock %}
{% block content %}
<h1>Your Profile</h1>
<p>Here are your details...</p>
{% endblock %}
The rendered <title> will now be “My Amazing Site - User Profile”. You’re augmenting, not nuking from orbit. This is absolutely critical for things like appending page-specific stylesheets to a block in the <head> or adding extra scripts before the </body> tag without wiping out the ones the base template already loaded.
Scoped Blocks and The Inheritance Quirk
Here’s a gotcha that feels like a design oversight but you just have to live with it: blocks are scoped to the inheritance chain, not to the file. You cannot define a new block inside another block in a child template and expect to override that nested block in a grandchild template. The block must be defined at the top level in the base template.
If base.html has:
{% block main %}
<div class="sidebar">{% block sidebar %}{% endblock %}</div>
<div class="content">{% block page_content %}{% endblock %}</div>
{% endblock %}
You can override sidebar or page_content in your child template. But you cannot override a sidebar block you define inside your child’s override of main. Jinja won’t see it. The solution is to plan your block structure in the base template. Annoying? A bit. But it forces a cleaner, more predictable architecture.
Controlling Whitespace Inside Blocks
Jinja is brutally honest about whitespace. If you indent your block content, that indentation will be in the final output. Sometimes it doesn’t matter (HTML mostly doesn’t care), but sometimes it absolutely does (inside a <pre> tag or for minified output).
To control this, use whitespace modifiers. A minus sign (-) strips whitespace inside the tag.
{% block footer -%} {# Note the -% at the start #}
<p>Copyright 2023</p>
{%- endblock %} {# And the -% at the end #}
The minus on the endblock is especially crucial. Without it, you’d often get a newline after the block content, which can lead to unexpected space in your HTML output, messing with inline elements. It’s a small thing, but it separates the pros from the amateurs.
Best Practice: Structure Your Blocks Thoughtfully
Think of your base template as an API. Every block is a public method your child templates can call. Name them well and purposefully. scripts, styles, head_content, main_content, footer_links – these are all good, specific names. A block named content is fine for a simple site, but it’s a nightmare for a complex one where you might want to inject something into the head on one specific page.
The most common pitfall is creating a base template that’s too rigid. You want a solid foundation, not a straightjacket. Leave plenty of hooks. And for the love of all that is good, use {{ super() }} in your blocks when you’re building a multi-level inheritance structure. It makes your templates far more composable and prevents you from having to duplicate code in every single child template just to get the base’s functionality back.