Statac

Shape the site

Data files

Data files hold what belongs to no one page, such as a site’s navigation or the people who write for it.

Put values in data/

data/
  links.yaml
  people.json
  lists/
    books.yaml

data/ sits at the top of the site’s folder. A template reaches each file under site.data:

File In a template
data/links.yaml site.data.links
data/people.json site.data.people
data/lists/books.yaml site.data.lists.books

Statac reads files ending .yaml, .yml or .json. A value is what YAML or JSON makes of it: 42 is a number, true is true, and anything in quotes is text. A date is text too, except on a page made from data, where it is the page’s date.

Show them in a template

data/links.yaml:

- title: Link 1
  address: https://example.org/
- title: Link 2
  address: /about/

templates/base.html:

<ul>
{% for link in site.data.links %}
  <li><a href="{{ link.address }}">{{ link.title }}</a></li>
{% endfor %}
</ul>

A name that can’t follow a dot goes in brackets: site.data.words["en-GB"].

How a name with dots is read

Written Read as
site.data.people data/people.yaml, .yml or .json
site.data.people.staff data/people/staff.yaml where data/people/ is a folder; else the key staff: in data/people.yaml

A template, an each: line and a declaration such as one of data.people.staff all read a name this way.

What Statac won’t read

In data/ What happens
A file with another ending, such as .csv It is left out.
A file whose name starts with a dot It is passed over without a word.
Two things at one name: people.yaml beside people/, or beside people.json The build stops.
YAML’s merge key, << The build stops. Write the fields out in full.
A key written twice in one object The build stops.

Records and their fields

data/people.yaml:

person-1:
  title: Person 1
  role: Role 1
person-2:
  title: Person 2
  role: Role 2

A table such as this holds records, each under a name of its own. A table written as a list, as data/links.yaml is, holds its records in order, with no names.

The fields statac.yaml declares for pages, such as fields.role: required text, hold for each page made from a record.

A field that names a record

In statac.yaml:

fields.author: one of data.people

In content/post-1.md:

---
title: Post 1
author: person-1
---

Each author: is checked against the names in data/people.yaml. A template is handed the name as written, so site.data.people[page.author].role gives the record’s role.

Records in order have no names to check. Front matter has fields that name a page.

Make a page for each item

In content/people.md:

---
title: People
each: data.people
permalink: /people/{key}/
layout: person
---
The people who write here.

This makes /people/person-1/ and /people/person-2/, each built with templates/person.html. The page itself is published at /people/, and speaks for the list of the pages it made: page.posts holds those with a date, and page.list.pages all of them.

Line What it does
each data. and the table’s name, read as a template reads it: data.people, data.people.staff.
permalink Needed: the pattern for each made page’s address, with at least one piece. A record’s own permalink: is its address instead.
layout The template of the pages made, not the page’s own.
type The page’s own template. Otherwise the page is built with folder.html, or home.html for content/index.md.
pages. lines Handed down to the pages made. pages.layout: counts only where the page has no layout:, and pages.permalink: is never the pattern.
index_page false publishes nothing at the page’s own address.

A made page is built with the first of:

  1. the record’s own layout:;
  2. a template named by type:, the record’s own or one handed down with pages.type:;
  3. the each: page’s layout:;
  4. a pages.layout: from the each: page, the folders above it, or statac.yaml;
  5. templates/page.html.

Pieces of the address

Piece Filled with
{key} The record’s name, made ready for an address.
{name} The record’s name, exactly as written.
{slug} The record’s name, or else its title, made ready for an address.
{year}, {month}, {day} Its date.
{folder} The folder holding the each: page, and nothing at the top of content/.
Any field, such as {isbn} or {title} Its text or number, made ready for an address. {date} gives 2026-01-05.

Records in order have no names, so no {key} or {name}.

What a made page is

  • A date: makes it a post, in the each: page’s list, the lists of the folder holding that page and every folder above it, site.posts, the site’s feed, and its tags and series. On an index.md with each:, page.posts holds the made posts alone, and page.folder.posts holds them with the folder’s own.
  • A draft, or a record dated after today, gets a page only where the run builds drafts: under statac serve, or with --drafts.
  • page.key is its record’s name, and page.source the data file.

each: site.posts makes a page for each post instead: see a page for each post.

Where next?

Something unclear or wrong? Open an issue on GitHub.