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:

VariableDescription
loop.indexCurrent iteration (1-based)
loop.index0Current iteration (0-based)
loop.firsttrue on first iteration
loop.lasttrue 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 #

FieldTypeDescription
paginator.pagesarrayPages for the current pagination page
paginator.current_indexintCurrent page number (1-based)
paginator.number_pagersintTotal number of pagination pages
paginator.previousstring or nullURL of the previous page
paginator.nextstring or nullURL of the next page
paginator.firststringURL of the first page
paginator.laststringURL 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>&copy; {{ 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 #