Template Inheritance¶
miki-template supports Django-style template inheritance via {% extends %} and {% block %}. This lets you build layout hierarchies where child templates override parent blocks.
Table of Contents¶
Basic Inheritance¶
base.html¶
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Default Title{% endblock %}</title>
</head>
<body>
<header>{% block header %}Default Header{% endblock %}</header>
<main>{% block content %}Default Content{% endblock %}</main>
<footer>{% block footer %}Default Footer{% endblock %}</footer>
</body>
</html>
child.html¶
{% extends "base.html" %}
{% block title %}My Page{% endblock %}
{% block content %}
<h1>Hello, {{ user.name }}!</h1>
{% for item in items %}
<p>{{ item }}</p>
{% endfor %}
{% endblock %}
Key behaviors:
-
The child template's text outside
{% block %}tags is ignored — only the blocks are used to override the parent. -
Any blocks not overridden in the child use the parent's default content.
-
The parent is located using the
viewsoption (or Express'sviewsdirectory).
block.super¶
Inside a block, {{ block.super }} renders the parent template's version of that block. This is useful for augmentation rather than replacement.
If base.html's content block is <p>Original</p>, the output is:
Real-world sidebar that adds to the parent:
<!-- base.html -->
{% block sidebar %}
<ul class="nav">
<li><a href="/">Home</a></li>
</ul>
{% endblock %}
<!-- admin.html -->
{% extends "base.html" %}
{% block sidebar %}
{{ block.super }}
<li><a href="/admin">Admin Panel</a></li>
{% endblock %}
Multi-Level Inheritance¶
Inheritance chains can be arbitrarily deep:
Each level can override blocks from its parent, and {{ block.super }} traverses the chain correctly.
Three-level example¶
base.html:
child.html:
{% extends "base.html" %}
{% block content %}
<h2>Child content</h2>
{{ block.super }}
{% endblock %}
grandchild.html:
{% extends "child.html" %}
{% block content %}
<h1>Grandchild content</h1>
{{ block.super }}
{% endblock %}
Rendering grandchild.html produces:
Rendering a Single Block¶
Compile a template and render only one block — useful for AJAX or HTMX responses where you only need a portion of the page:
renderBlock behavior¶
-
If the block is not found, throws
Block 'blockName' not found in template. -
If the block has no overrides, renders the default body.
-
If the block has overrides, renders the child-most block first, then traverses up for
{{ block.super }}.
Real-world HTMX use case:
<!-- layout.html -->
{% block main %}
<div id="main-content">
<!-- default content -->
</div>
{% endblock %}
// Return only the main block for an AJAX update
app.get('/ajax/content', (req, res) => {
const compiled = miki.compile(template, { views: './views' });
res.send(compiled.renderBlock('main', { user: req.user }));
});
block Default Behavior¶
If a child template does not override a block, the parent's default content is rendered:
<!-- child.html -->
{% extends "base.html" %}
{% block content %}Main content{% endblock %}
<!-- sidebar block is not overridden, so "Default sidebar" is used -->
Dynamic extends¶
You can use expressions in extends for device-specific or conditional layouts:
Path Traversal Protection¶
{% extends %} and {% include %} paths are validated to prevent directory traversal attacks:
The engine checks that resolved paths stay within the allowed views directories. An Error with message starting path traversal is thrown if a path escapes the views root.
Smart Template Discovery for Inheritance¶
When using setupExpress(), the engine automatically searches for parent templates in:
-
The configured
viewsdirectory -
Nested
templates/directories inside the views root -
Subdirectories of the views root
-
App-style
app/templates/...,packages/*/templates/..., etc.
This means you can organize templates like:
project/
├── views/
│ ├── base.html
│ └── home.html
├── app/
│ └── templates/
│ └── admin/
│ └── dashboard.html
And {% extends "base.html" %} will be found regardless of where the child template lives.