Documentation
Writing and customizing templates
Everlock uses Tera as its template engine. Tera's syntax is similar to Jinja2 and Django templates. Templates live in the layouts/ directory of your site repository. This page explains the syntax, the context variables available in each template, and common customization patterns.
Template syntax basics
Output a variable
{{ page.title }}
Double braces output the value of a variable. Any HTML in the value is escaped by default.
Output raw HTML safely
{{ page.content | safe }}
The | safe filter tells Tera the value is already trusted HTML and should not be escaped. Use this for rendered Markdown content.
Conditionals
{% if page.git_date %}
Updated {{ page.git_date }}
{% endif %}
{% if page.tags %}
{% for tag in page.tags %}
{{ tag }}
{% endfor %}
{% endif %}
Loops
{% for link in site.nav.links %}
{{ link.label }}
{% endfor %}
Default values
{{ p.git_date | default(value=p.date) | default(value="") }}
Filters chain left to right. | default(value=x) substitutes x when the left side is empty or undefined.
String filters
{{ page.title | upper }}
{{ tag | replace(from="-", to=" ") | title }}
Common Tera filters: upper, lower, title, trim, replace, truncate, length, first, last.
Date helpers
Everlock adds a small date layer on top of Tera for Markdown sites:
Now: {{ now() }}
Today: {{ today() }}
{% if page.date is before(date=now()) %}
This page date is in the past.
{% endif %}
{{ page.date | date_format(format="%Y-%m-%d") }}
Accepted input formats:
YYYY-MM-DD- RFC3339 timestamps such as
2026-06-11T08:30:00Z
today() and date-only comparisons use the site timezone from site.toml. If
no timezone is set, Everlock falls back to UTC.
Filtering lists
The where filter keeps the items of a list whose attribute matches a value —
plain equality for scalar attributes, containment for list attributes such as
tags:
{% set git = all_pages | where(attribute="tags", value="git") %}
{% set updates = all_pages | where(attribute="section", value="/updates") %}
Dotted paths reach nested fields, so custom frontmatter is filterable too:
{% set news = all_pages | where(attribute="extra.kind", value="news") %}
Items missing the attribute are dropped. where composes with the other list
filters (sort, reverse, first) and with paginate() below.
Pagination
A list layout controls its own pagination with the paginate() function. It
slices the listing's full item set by the requested page (?page=N) and
returns the current page plus a navigation model:
{% set pager = paginate(by=50) %}
{% for p in pager.items %}
{{ p.title }}
{% endfor %}
{% if pager.pagination.total_pages > 1 %}
{% if pager.pagination.has_prev %}Previous{% endif %}
{{ pager.pagination.current_page }} / {{ pager.pagination.total_pages }}
{% if pager.pagination.has_next %}Next{% endif %}
{% endif %}
Called with only by=, paginate() works on the listing's complete
newest-first item set — the same collection that feeds pages — so the
layout's page size wins over the site-wide paginate_by. An items= argument
paginates a list the template assembled itself, which composes with filtering
and sorting:
{% set pager = paginate(items=my_filtered_list, by=10) %}
by=0 turns paging off: pager.items holds every item and pager.pagination
is null. The function is available in section listings, tag pages, and
taxonomy pages. Templates that render the prepared pages variable keep the
site-wide paginate_by behavior unchanged.
Independent paginators on one page
A param= argument names the query parameter a paginator tracks (default
page). Combined with where, one layout paginates several filtered lists
independently — each list's Previous/Next links change only their own
parameter and keep the rest of the query string, so navigating one section
leaves the others where they are:
Git articles
{% set git = all_pages | where(attribute="tags", value="git") %}
{% set g = paginate(items=git, by=10, param="git_page") %}
{% for p in g.items %}{{ p.title }}{% endfor %}
{% if g.pagination.total_pages > 1 %}
{% if g.pagination.has_next %}More{% endif %}
{% endif %}
UI articles
{% set ui = all_pages | where(attribute="tags", value="ui") %}
{% set u = paginate(items=ui, by=10, param="ui_page") %}
{% for p in u.items %}{{ p.title }}{% endfor %}
{% if u.pagination.total_pages > 1 %}
{% if u.pagination.has_next %}More{% endif %}
{% endif %}
Requesting ?git_page=2 shows page 2 of the git list while the UI list stays
on page 1; the git section's "More" link becomes ?git_page=3 with any other
parameters preserved.
Template inheritance
Every template in Everlock extends base.html. The pattern uses {% extends %} and {% block %}:
layouts/base.html defines named blocks:
{% block title %}{{ page.title }} | {{ site.title }}{% endblock %}
{% for link in site.nav.links %}
{{ link.label }}
{% endfor %}
{% block main %}{% endblock %}
layouts/single.html fills in the block:
{% extends "base.html" %}
{% block main %}
{{ page.title }}
{{ page.content | safe }}
{% endblock %}
Any block not overridden in the child template renders from the parent. A child can call {{ super() }} to include the parent block's content and then add to it.
Context variables reference
Available in all templates
| Variable | Type | Content |
|---|---|---|
site.title | string | Site title from site.toml |
site.description | string | Site description from site.toml |
site.base_url | string | Base URL from site.toml |
site.timezone | string | Optional site timezone used by today() and date formatting |
site.nav.links | list | Navigation links, each with .label and .url |
data | map | All .toml files from the data/ directory |
In single.html (content pages)
| Variable | Type | Content |
|---|---|---|
page.title | string | Title from frontmatter |
page.content | string | Rendered HTML of the page body |
page.summary | string | HTML of content before <!--more-->, or empty |
page.tags | list | Tag strings |
page.date | string | Frontmatter date |
page.git_date | string | Date of the last git commit for this file |
page.url | string | Page URL path, e.g. /docs/reference |
page.extra | map | Scalar extra frontmatter fields |
page.related | list | Up to 5 related pages (by shared tags), each with .title, .url, .tags |
page.prev_page | object | Previous page in a series, with .title and .url |
page.next_page | object | Next page in a series, with .title and .url |
page.uses_mermaid | bool | Whether the page contains a Mermaid diagram |
In list.html and custom section templates
| Variable | Type | Content |
|---|---|---|
page.title | string | Section title from _index.md or URL path |
page.content | string | Rendered HTML from _index.md body |
pages | list | Child pages, each with .title, .url, .snippet, .tags, .date, .git_date |
pagination | object | Pagination state (only when paginate_by is set in site.toml) |
pagination.current_page | int | Current page number |
pagination.total_pages | int | Total number of pages |
pagination.has_prev | bool | Whether a previous page exists |
pagination.has_next | bool | Whether a next page exists |
pagination.prev_url | string | URL of the previous page |
pagination.next_url | string | URL of the next page |
Example: customizing base.html
Here is a base.html that adds a sticky header with a brand logo, a search link, and a footer:
{% block title %}{{ page.title }} | {{ site.title }}{% endblock %}
{{ site.title }}
{% for link in site.nav.links %}
{{ link.label }}
{% endfor %}
Search
{% block main %}{% endblock %}
{{ site.title }} · RSS
Example: a content page with related links
layouts/single.html showing the page, its tags, and related pages in a sidebar:
{% extends "base.html" %}
{% block main %}
{{ page.title }}
{% if page.git_date %}
Last updated {{ page.git_date }}
{% endif %}
{{ page.content | safe }}
{% if page.tags %}
{% for tag in page.tags %}
{{ tag }}
{% endfor %}
{% endif %}
{% if page.prev_page or page.next_page %}
{% if page.prev_page %}
← {{ page.prev_page.title }}
{% endif %}
{% if page.next_page %}
{{ page.next_page.title }} →
{% endif %}
{% endif %}
{% if related %}
Related
{% for p in related %}
{{ p.title }}
{% endfor %}
{% endif %}
{% endblock %}
Example: a compact section listing
A layouts/list.html that shows a compact table-style listing instead of cards:
{% extends "base.html" %}
{% block main %}
{{ page.title }}
{% if page.content %}
{{ page.content | safe }}
{% endif %}
PageUpdatedTags
{% for p in pages %}
{{ p.title }}
{{ p.git_date | default(value="—") }}
{% for tag in p.tags %}
{{ tag }}
{% endfor %}
{% endfor %}
{% if pagination is defined and pagination.total_pages > 1 %}
{% if pagination.has_prev %}Previous{% endif %}
{{ pagination.current_page }} / {{ pagination.total_pages }}
{% if pagination.has_next %}Next{% endif %}
{% endif %}
{% endblock %}
Example: using data/ for dynamic content
A template that lists team members from data/team.toml:
data/team.toml:
[[members]]
name = "Alice"
role = "Maintainer"
link = "https://github.com/alice"
[[members]]
name = "Bob"
role = "Contributor"
link = "https://github.com/bob"
In any template:
{% for member in data.team.members %}
{{ member.name }}
{{ member.role }}
{% endfor %}
data is a map keyed by filename (without .toml), so data/team.toml becomes data.team.
Example: page-level extra fields
Extra frontmatter fields are available in templates via page.extra:
In single.html:
{% if page.extra.release_version %}
Version {{ page.extra.release_version }}
{% endif %}
Tips
Use | safe only on values you control. Rendered Markdown (page.content) and shortcode output is already sanitized by Everlock. Do not use | safe on user-submitted or external data.
Check existence before accessing nested fields. If a page has no tags, page.tags is an empty list (safe to loop over). If a page has no prev_page, the variable is null — use {% if page.prev_page %} before accessing .url or .title on it.
Template changes are live on next request. Everlock reads templates from the store on each request. Push a template change with git push and reload the page to see it.