Zorto uses the Tera template engine, which has Jinja2-like syntax.

Template hierarchy #

Zorto looks for these templates (themes provide defaults for all of them):

TemplateUsed for
base.htmlBase layout all others extend
index.htmlSite homepage (content/_index.md)
section.htmlSection pages (content/*/_index.md)
page.htmlIndividual pages
404.htmlNot-found page
{taxonomy}/list.htmlTaxonomy index (e.g., tags/list.html for /tags/)
{taxonomy}/single.htmlSingle taxonomy term (e.g., tags/single.html for /tags/rust/)

Template context #

Each template receives context variables:

VariableAvailable inDescription
pagepage.htmlCurrent page object (title, content, date, permalink, extra, etc.)
sectionsection.html, index.htmlCurrent section object (title, pages, subsections, etc.)
configAllSite configuration (config.title, config.extra, etc.)
paginatorPaginated sectionsPagination info (pages, current_index, number_pagers)

Use page.permalink or section.permalink to get the current page’s full URL.

Custom functions #

FunctionDescription
get_url(path)Get the permalink for a path
get_section(path)Load a section and its pages
get_taxonomy_url(kind, name)URL for a taxonomy term
now()Current timestamp

Example:

{% set posts = get_section(path="posts/_index.md") %}
{% for page in posts.pages %}
  <a href="{{ page.permalink }}">{{ page.title }}</a>
{% endfor %}

Worked example #

Here’s a complete page.html showing how template variables, filters, and blocks work together:

{% extends "base.html" %}

{% block content %}
<article>
  <h1>{{ page.title }}</h1>

  {% if page.date %}
  <time>{{ page.date | date(format="%B %d, %Y") }}</time>
  {% endif %}

  {% if page.taxonomies.tags %}
  <div class="tags">
    {% for tag in page.taxonomies.tags %}
    <a href="{{ get_taxonomy_url(kind="tags", name=tag) }}">{{ tag }}</a>
    {% endfor %}
  </div>
  {% endif %}

  {{ page.content | safe }}
</article>
{% endblock %}

Filters and tests #

Tera provides filters (transform values) and tests (check conditions). Common ones:

{{ page.date | date(format="%B %Y") }}  <!-- "January 2026" -->
{{ pages | slice(start=0, end=5) }}     <!-- first 5 items -->
{{ count | pluralize }}                  <!-- "s" if count != 1 -->
{% if path is starting_with("/docs") %}...{% endif %}

See Tera’s documentation for the full list of filters and tests.

Blocks and inheritance #

Parentbase.html — shared layout (nav, footer, HTML skeleton)extends
Childindex.html, section.html, page.html — fill in specific blocksoverrides

Child templates extend base.html and override only the blocks they need.

Templates use block inheritance:

{# base.html #}
<html>
<body>
  {% block content %}{% endblock %}
</body>
</html>
{# page.html #}
{% extends "base.html" %}
{% block content %}
  <h1>{{ page.title }}</h1>
  {{ page.content | safe }}
{% endblock %}

Discovering theme templates #

To see what templates and blocks a theme provides, check the theme source in the Zorto repository under crates/zorto-core/themes/<theme-name>/templates/. Each theme defines the same set of templates with different styling.

Macros #

Define reusable template fragments:

{% macro card(title, url) %}
  <a href="{{ url }}" class="card">{{ title }}</a>
{% endmacro %}

{{ self::card(title="Home", url="/") }}

Further reading #