Skip to content

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 views option (or Express's views directory).

block.super

Inside a block, {{ block.super }} renders the parent template's version of that block. This is useful for augmentation rather than replacement.

{% extends "base.html" %}



{% block content %}

  <h1>My Content</h1>

  {{ block.super }}

{% endblock %}

If base.html's content block is <p>Original</p>, the output is:

<h1>My Content</h1>

<p>Original</p>

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:

base.html

  └── child.html

        └── grandchild.html

Each level can override blocks from its parent, and {{ block.super }} traverses the chain correctly.

Three-level example

base.html:

<html>

<body>

  {% block content %}Base content{% endblock %}

</body>

</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:

<html>

<body>

  <h1>Grandchild content</h1>

  <h2>Child content</h2>

  Base content

</body>

</html>

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:

const { compile } = require('miki-template');



const compiled = compile(childTemplateStr, { views: './templates' });

const partialHtml = compiled.renderBlock('content', context);
import { compile } from 'miki-template';



const compiled = compile(childTemplateStr, { views: './templates' });

const partialHtml = compiled.renderBlock('content', context);

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:

<!-- base.html -->

<html>

<body>

  {% block sidebar %}Default sidebar{% endblock %}

</body>

</html>
<!-- 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:

{% extends device_type|default:"base.html" %}
{% extends user.theme|default:"default.html" %}

Path Traversal Protection

{% extends %} and {% include %} paths are validated to prevent directory traversal attacks:

{% extends "../../etc/passwd" %}  {# REJECTED #}

{% include "../../secrets" %}      {# REJECTED #}

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 views directory

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

Next Steps