Documentation
Markdown site directory structure
A Markdown site in Everlock is a Git repository. Every file you push is either served directly, rendered as a page, or used as site infrastructure. This page explains what each file and directory does, why it exists, and how Everlock treats it.
The rules described here only apply to sites running in markdown mode. In html mode, all files are served as-is and none of this structure is interpreted.
Top-level overview
A minimal but fully functional site might look like this:
my-site/
├── site.toml
├── index.md
├── about.md
├── assets/
│ └── style.css
├── images/
│ └── logo.svg
├── layouts/
│ ├── base.html
│ ├── single.html
│ └── list.html
├── shortcodes/
│ └── callout.html
└── docs/
├── _index.md
├── getting-started.md
└── reference.md
More complex sites add data/, format variant templates, and additional section directories.
site.toml — site-wide configuration
site.toml at the root configures global metadata and structural behavior. Everlock reads it on every request; you do not need to restart to see changes.
title = "My Site"
description = "A site served by Everlock"
base_url = "https://example.com"
timezone = "Europe/Berlin"
paginate_by = 12
sitemap = true
ignore_dirs = ["out", "vendor"]
[nav]
links = [
{ label = "Home", url = "/" },
{ label = "Docs", url = "/docs/" },
{ label = "About", url = "/about" },
]
[markdown]
syntax_highlighting = true
highlight_style = "InspiredGitHub"
[taxonomies]
category = "categories"
| Field | Purpose |
|---|---|
title | Site name used in <title> tags and templates |
description | Default meta description for all pages |
base_url | Absolute URL base, used for sitemap and RSS |
timezone | Optional IANA timezone name used by template date helpers such as today() and date_format; defaults to UTC when omitted |
paginate_by | Items per page on list pages; omit to disable pagination. A list layout can set its own page size with the paginate() template function |
build_future | Set to true to render pages whose frontmatter date lies in the future; defaults to false, which holds them back until their date passes |
sitemap | Set to true to serve /sitemap.xml; defaults to false. Requires base_url for correct absolute URLs. Each URL's <lastmod> is the page's frontmatter date, or the date of its last content change when none is set |
nav.links | Navigation items available to templates as site.nav.links |
markdown.syntax_highlighting | Toggle code block syntax highlighting |
markdown.highlight_style | Name of a Syntect highlight theme |
taxonomies | Custom taxonomy fields; each key maps a URL prefix to a frontmatter field |
ignore_dirs | Root-relative directories to skip when scanning markdown and serving files; defaults to [] |
site.toml is internal infrastructure. It is never served over HTTP.
Ignored directories
In markdown mode Everlock automatically skips any path component that starts
with a dot (for example .git/, .cursor/, .venv/). Files under those
directories never become pages, never appear in section listings, and are not
served as static assets.
Use ignore_dirs to extend that filter to non-hidden directories — typical
candidates are generated output folders or vendored dependencies you keep in
the repo for tooling but do not want published:
ignore_dirs = ["out", "vendor", "generated/cache"]
Entries are root-relative and match a directory and everything beneath it.
index.md — the root page
The file index.md at the root of the repository becomes the page at /. It is the only page that lives at the root without a directory.
index.md → /
Example:
If you want a section directory to have its own landing page, use _index.md inside that directory instead (see below).
_index.md — section metadata and landing pages
_index.md is a special file that lives inside a section directory. It is not a regular content page. It provides:
- a title for the generated section listing
- optional introductory content displayed above the list of child pages
- an optional layout override for the section listing template
- a draft flag to suppress the section
docs/
├── _index.md ← section metadata
├── getting-started.md
└── reference.md
When you visit /docs/, Everlock:
- reads
docs/_index.mdfor the section title and intro - lists all child
.mdfiles indocs/as a generated page index - uses the
layout:from_index.md(or falls back tolist.html) - never exposes
_indexitself as a link in the listing
Example _index.md:
Without a _index.md, the section title defaults to the URL path and no intro text is shown. The listing still works.
Choosing a section layout
By default, section listings use list.html. To use a different template, set layout: in _index.md:
This tells Everlock to render the /updates/ listing with layouts/blog_list.html instead of layouts/list.html. The blog_list.html template receives the same page, pages, and pagination context variables as list.html.
Regular content pages
Any .md file that is not index.md or _index.md becomes a content page at a URL derived from its path:
about.md → /about
docs/reference.md → /docs/reference
updates/2026-01-15-release.md → /updates/2026-01-15-release
Subdirectories become URL segments automatically. You do not need to register routes.
Requests for a legacy .html address are caught automatically: when no such
file exists in the repo but the markdown source does, Everlock redirects to the
extensionless route — /about.html redirects to /about, and
/docs/index.html redirects to /docs. Sites migrated from generators that
publish .html URLs (such as Hugo with uglyurls) keep their inbound links
working without any redirect configuration.
Frontmatter
Every page can have a YAML frontmatter block at the top:
-
| Field | Meaning |
|---|---|
title | Page title shown in <title>, headings, and listings |
date | Publication date; used for sorting and RSS. A future date holds the page back until it passes |
tags | List of tags; powers /tags/ routes automatically |
draft: true | Hides the page from listings, search, RSS, and direct access |
layout | Template name (without .html) inside layouts/; defaults to single.html |
description | Per-page meta description; falls back to site.toml description |
series | List of series names this page belongs to; enables prev/next navigation |
Any key not in the set above becomes an extra field available to templates as page.extra.your_key.
Any key whose value is a YAML list of strings becomes available as page.extra_lists.your_key. This is how series and custom taxonomies work.
Draft pages
Setting draft: true excludes the page from all generated output: it will not appear in listings, the search index, RSS, sitemaps, or tag pages. It is also not accessible over HTTP directly.
Future pages
A page dated in the future is scheduled, not published: it stays out of listings, search, RSS, sitemaps, and tag pages, and direct requests answer 404. Pages render per request, so the page goes live the moment its date passes — push the post ahead of time and the site publishes it on schedule.
The date resolves in the site timezone (a plain YYYY-MM-DD counts as midnight there), and an RFC3339 value like 2027-01-01T09:00:00+01:00 schedules to the minute. Setting build_future = true in site.toml renders future pages everywhere — useful on a staging site for previewing scheduled posts.
<!--more--> — the read-more split
Placing the HTML comment <!--more--> inside page content splits the page into a visible summary and the rest of the article. The content before <!--more--> becomes the page summary available to templates and is used in listing cards.
layouts/ — templates
The layouts/ directory holds Tera templates that control how pages are rendered. Every file in layouts/ is registered as a template, using its path relative to layouts/ as the name. The file extension is the output format — .html for web pages, .vcf for vCards, .json for JSON, etc.
layouts/
├── base.html ← outer shell: <html>, <head>, <body>, nav, footer
├── single.html ← individual content pages
├── list.html ← section listings and tag pages
├── search.html ← the /search page
├── 404.html ← optional custom error page
├── blog_list.html ← example: a custom blog-style section listing
├── person.html ← example: HTML layout for a contact page
└── person.vcf ← example: vCard format variant for the same page
layouts/ and its contents are never served over HTTP.
base.html
The outer HTML shell. All other templates typically {% extends "base.html" %} and fill in a {% block main %} block. base.html has access to site and page context variables.
single.html
Used for individual content pages. Receives:
| Variable | Content |
|---|---|
page.title | Page title from frontmatter |
page.content | Rendered HTML of the page body |
page.tags | List of tag strings |
page.date | Frontmatter date |
page.git_date | Date of last git commit for this file |
page.summary | Rendered HTML of content before <!--more--> |
page.related | Up to 5 pages sharing tags with this page |
page.prev_page | Previous page in the same series, if any |
page.next_page | Next page in the same series, if any |
page.extra | Map of scalar extra frontmatter fields |
site | Parsed site.toml |
list.html
Used for section listings, tag pages, and taxonomy pages. Receives:
| Variable | Content |
|---|---|
page.title | Section title (from _index.md or path) |
page.content | Rendered HTML from _index.md body |
pages | List of child page objects, each with title, url, snippet, tags, date, git_date |
pagination | Pagination object (only present when paginate_by is set) |
site | Parsed site.toml |
A custom section listing template such as blog_list.html receives exactly the same variables and can be selected via layout: in _index.md.
404.html
An optional custom error page, rendered with status 404 for any request that matches no route. Like every other layout it is a normal Tera template and can {% extends "base.html" %}, so the error page keeps the site's header and navigation. Receives:
| Variable | Content |
|---|---|
page.title | Page not found |
page.path, page.url | The requested path |
site | Parsed site.toml |
Without the file, Everlock answers with a plain-text 404.
Format variants
A format variant lets a single Markdown file be served in multiple content types. The page declares a layout: in its frontmatter; Everlock uses that name as the template family and the requested file extension to pick the right template.
With this frontmatter:
/contacts/alicerenderslayouts/person.html→ HTML page/contacts/alice.vcfrenderslayouts/person.vcf→ vCard/contacts/alice.jsonrenderslayouts/person.json→ JSON
layouts/person.vcf would look like plain vCard text using Tera syntax:
BEGIN:VCARD
VERSION:3.0
FN:{{ page.title }}
EMAIL:{{ page.extra.email }}
END:VCARD
Supported output content types: json, xml, vcf, atom, txt, csv, ical, ics.
shortcodes/ — reusable content components
The shortcodes/ directory holds Tera template snippets that can be called from inside Markdown content using the `
` syntax. They let you embed structured HTML without writing it directly in your Markdown.
shortcodes/ is never served over HTTP.
Built-in shortcodes
Two shortcodes are available without any template files:
mermaid — diagrams
Renders a Mermaid diagram. You can also load diagram source from a file in the repository:
Mermaid is rendered in the browser via the Mermaid JavaScript library, which Everlock injects automatically when the page uses this shortcode.
link — internal links
Renders a plain link path. Useful for building links programmatically inside templates.
Custom shortcodes
Any file in shortcodes/ becomes a callable shortcode. The template receives an args object with the named parameters you pass.
Example: shortcodes/callout.html
{% if args.title %}{{ args.title }}{% endif %}
{{ args.text }}
Called in Markdown as:
assets/ — static files
The assets/ directory holds static files like CSS, JavaScript, and fonts. These are served as-is under the /assets/ URL prefix.
assets/
├── style.css → /assets/style.css
└── search.js → /assets/search.js
There is nothing special about the name assets/. Any directory that contains files without a .md extension will have those files served directly. assets/ is simply the conventional name.
images/ — image files
Images and other media live in images/ by convention and are served directly:
images/
├── logo.svg → /images/logo.svg
└── hero.png → /images/hero.png
Reference them in Markdown like any regular path:
data/ — structured data for templates
The data/ directory holds .toml files that are loaded and made available to all templates as the data context variable.
data/
├── team.toml
└── releases.toml
data/team.toml:
[[members]]
name = "Alice"
role = "Maintainer"
[[members]]
name = "Bob"
role = "Contributor"
In any template:
{% for member in data.team.members %}
{{ member.name }} — {{ member.role }}
{% endfor %}
Only top-level .toml files in data/ are loaded. Subdirectories are ignored. data/ is never served over HTTP.
Auto-generated routes
Everlock generates several routes automatically for every Markdown site. These do not need any files in the repository.
| Route | Content |
|---|---|
/index.xml | RSS feed of all pages, sorted by date |
/sitemap.xml | XML sitemap of all pages with last-modified dates (only when sitemap = true in site.toml) |
/search | Search page (requires layouts/search.html) |
/search-index.json | JSON search index consumed by the search page |
/tags/ | Tag listing page |
/tags/<tag> | Pages matching a specific tag |
Custom taxonomy routes are also generated automatically based on site.toml:
[taxonomies]
category = "categories"
This generates /category/ and /category/<value> routes for any page with a categories: frontmatter field.
Internal paths — never served
These paths are reserved for site infrastructure. Requests to them return 404 regardless of what exists in the repository:
| Path | Purpose |
|---|---|
site.toml | Site configuration |
layouts/ | HTML templates |
shortcodes/ | Shortcode templates |
data/ | Data files for templates |
Series — prev/next navigation
Pages can be linked into an ordered series using the series: frontmatter list. All pages sharing the same series name are sorted by date ascending, and each page gets prev_page and next_page context variables pointing to its neighbors.
- -
In single.html:
{% if page.prev_page %}
← {{ page.prev_page.title }}
{% endif %}
{% if page.next_page %}
{{ page.next_page.title }} →
{% endif %}
Related pages
Every page automatically gets a related list in its template context. Everlock finds up to five other pages that share tags with the current page, ranked by the number of tags in common. No configuration is needed; it follows from your tag usage.
{% if related %}
Related
{% for p in related %}
{{ p.title }}
{% endfor %}
{% endif %}
Full example: a blog
Here is a concrete repository layout for a simple blog, with annotations showing what Everlock does with each file:
my-blog/
├── site.toml ← site config, nav, pagination
├── index.md ← serves /
├── about.md ← serves /about
├── assets/
│ └── style.css ← serves /assets/style.css
├── images/
│ └── avatar.jpg ← serves /images/avatar.jpg
├── layouts/
│ ├── base.html ← shared HTML shell
│ ├── single.html ← article pages
│ ├── list.html ← default section listing
│ └── blog_list.html ← date-first post index
├── shortcodes/
│ └── callout.html ←
shortcode
└── posts/
├── _index.md ← title: "Posts", layout: blog_list
├── 2026-01-10-hello.md ← serves /posts/2026-01-10-hello
└── 2026-02-04-followup.md ← serves /posts/2026-02-04-followup
Visiting /posts/ renders a blog-style index using blog_list.html, with each post listed by date and title. The _index.md controls the section heading and which template is used. Each post file is a regular content page served by single.html.