Advanced templating
Go beyond basic templates with macros, block inheritance, loops, pagination, and dynamic data from config.extra.
Block inheritance #
Every Zorto template system starts with a base layout. Child templates extend it and override specific blocks.
base.html defines the skeleton:
<html> <head> <title>{% block title %}{{ config.title }}{% endblock %}</title> </head> <body> <nav>{% block nav %}{% endblock %}</nav> <main>{% block content %}{% endblock %}</main> <footer>{% block footer %}{% endblock %}</footer> </body> </html>
page.html overrides only what it needs:
{% extends "base.html" %} {% block title %}{{ page.title }} | {{ config.title }}{% endblock %} {% block content %} <article> <h1>{{ page.title }}</h1> {{ page.content | safe }} </article> {% endblock %}
Blocks not overridden in the child template use the parent’s default content. You can nest extends multiple levels deep (e.g. page.html extends base.html, post.html extends page.html).
Calling the parent block #
Use super() to include the parent block’s content alongside your additions:
{% block footer %}
{{ super() }}
<p>Extra footer content</p>
{% endblock %}
Macros #
Macros are reusable template fragments with parameters. Define them in any template file and import them where needed.
Define a macro #
{% macro card(title, url, description="") %}
<a href="{{ url }}" class="card">
<h3>{{ title }}</h3>
{% if description %}
<p>{{ description }}</p>
{% endif %}
</a>
{% endmacro %}
Use a macro in the same file #
{{ self::card(title="Home", url="/") }}
{{ self::card(title="About", url="/about/", description="Learn more") }}
Import macros from another file #
Create templates/macros.html with your macro definitions, then import:
{% import "macros.html" as macros %}
{{ macros::card(title="Home", url="/") }}
For loops #
Iterate over arrays like section.pages, taxonomy terms, or any array value.
Basic loop #
{% for page in section.pages %}
<article>
<h2><a href="{{ page.permalink }}">{{ page.title }}</a></h2>
{% if page.date %}
<time>{{ page.date | date(format="%B %d, %Y") }}</time>
{% endif %}
</article>
{% endfor %}
Loop variables #
Tera provides these variables inside for loops:
| Variable | Description |
|---|---|
loop.index | Current iteration (1-based) |
loop.index0 | Current iteration (0-based) |
loop.first | true on first iteration |
loop.last | true on last iteration |
{% for page in section.pages %}
<div class="{% if loop.first %}featured{% endif %}">
{{ page.title }}
</div>
{% endfor %}
Limiting results #
Use the slice filter to show only a subset:
{% for page in section.pages | slice(end=3) %}
<!-- Only the first 3 pages -->
{% endfor %}
Pagination #
Sections with paginate_by set in their frontmatter provide a paginator object in the template context.
Section frontmatter #
+++ title = "Blog" sort_by = "date" paginate_by = 10 +++
Paginator fields #
| Field | Type | Description |
|---|---|---|
paginator.pages | array | Pages for the current pagination page |
paginator.current_index | int | Current page number (1-based) |
paginator.number_pagers | int | Total number of pagination pages |
paginator.previous | string or null | URL of the previous page |
paginator.next | string or null | URL of the next page |
paginator.first | string | URL of the first page |
paginator.last | string | URL of the last page |
Pagination template #
{% for page in paginator.pages %}
<article>
<h2><a href="{{ page.permalink }}">{{ page.title }}</a></h2>
</article>
{% endfor %}
<nav class="pagination">
{% if paginator.previous %}
<a href="{{ paginator.previous }}">Previous</a>
{% endif %}
<span>Page {{ paginator.current_index }} of {{ paginator.number_pagers }}</span>
{% if paginator.next %}
<a href="{{ paginator.next }}">Next</a>
{% endif %}
</nav>
Accessing config.extra #
The config object is available in every template. Custom values from the [extra] section of config.toml are accessible as config.extra.
Define custom values #
# config.toml [extra] author = "Cody" year = 2026 social = { github = "dkdc-io", twitter = "dkdc_io" } menu_items = [ { name = "Home", url = "/" }, { name = "Blog", url = "/posts/" }, ]
Use in templates #
<footer>
<p>© {{ config.extra.year }} {{ config.extra.author }}</p>
{% if config.extra.social %}
<a href="https://github.com/{{ config.extra.social.github }}">GitHub</a>
{% endif %}
</footer>
<nav>
{% for item in config.extra.menu_items %}
<a href="{{ item.url }}">{{ item.name }}</a>
{% endfor %}
</nav>
Other config fields #
Beyond extra, these top-level config fields are also available:
{{ config.base_url }} <!-- "https://example.com" -->
{{ config.title }} <!-- "My Site" -->
{{ config.description }} <!-- "Site description" -->
Conditional taxonomy rendering #
Render taxonomy values only when they exist on a page:
{% if page.taxonomies.tags %}
<div class="tags">
{% for tag in page.taxonomies.tags %}
<a href="{{ get_taxonomy_url(kind="tags", name=tag) }}" class="tag">
{{ tag }}
</a>
{% endfor %}
</div>
{% endif %}
{% if page.taxonomies.categories %}
<div class="categories">
{% for cat in page.taxonomies.categories %}
<a href="{{ get_taxonomy_url(kind="categories", name=cat) }}">
{{ cat }}
</a>
{% endfor %}
</div>
{% endif %}
Conditional content with tests #
Use Tera’s is keyword with custom tests:
{% if page.path is starting_with("/docs") %}
<!-- Show docs sidebar -->
{% endif %}
{% if page.draft %}
<div class="draft-banner">This is a draft</div>
{% endif %}
Loading sections dynamically #
Use get_section to pull content from other sections into any template:
{% set recent_posts = get_section(path="posts/_index.md") %}
<h2>Latest posts</h2>
{% for page in recent_posts.pages | slice(end=3) %}
<a href="{{ page.permalink }}">{{ page.title }}</a>
{% endfor %}
This is commonly used on the homepage (index.html) to show recent posts from a section.
Further reading #
- Templates concept — template hierarchy and context variables
- Template functions and filters — complete function and filter reference
- Taxonomies in depth — taxonomy template rendering