Data Model
Hwaro's template system centers on three core types: Site, Section, and Page. This page is a template-side reference — all properties and variables you can use when building templates. For how to write content and set front matter fields, see Writing.
Hierarchy
Site
├── Config (title, base_url, ...)
├── Pages[] (standalone pages)
├── Sections[]
│ ├── Pages[] (pages in section)
│ └── Subsections[]
│ ├── Pages[]
│ └── Subsections[] (recursive)
└── Taxonomies{}
└── Terms{}
└── Pages[]
Relationships
- A Site contains multiple Sections and standalone Pages
- A Section contains Pages and child Subsections
- Subsections can nest indefinitely
- Taxonomies group Pages by terms
Site
The root container. Configured in config.toml.
Properties
| Property | Type | Description |
|---|---|---|
| site.title | String | Site title |
| site.description | String | Site description |
| site.base_url | String | Base URL (no trailing slash) |
| site.pages | Array<Page> | All non-section pages |
| site.sections | Array<Section> | All section index pages |
| site.taxonomies | Object | All taxonomy groups and terms |
| site.data | Object | Data loaded from data/ directory |
| site.authors | Object | Aggregated author data |
Flat Aliases
| Variable | Equivalent |
|---|---|
| site_title | site.title |
| site_description | site.description |
| base_url | site.base_url |
Data Directory
Hwaro allows you to store auxiliary data in the data/ directory. Files ending in .yml, .yaml, .json, or .toml are automatically loaded and exposed via site.data.
File Structure
data/
├── authors.yml
├── products.json
└── config.toml
Accessing Data
Data is accessed by the filename (without extension).
For example, data/products.json:
[
{"name": "Widget", "price": 10},
{"name": "Gadget", "price": 20}
]
Can be accessed in templates:
{% for product in site.data.products %}
<h2>{{ product.name }}</h2>
<p>{{ product.price }}</p>
{% endfor %}
Site Authors
Hwaro automatically aggregates all authors defined in the authors front matter field (authors = ["id"]) into site.authors.
Defining Authors
You can enrich author data by creating a data/authors.yml (or .json, .toml) file. Keys must match the author IDs used in the page front matter.
content/my-post.md
---
title: "My Post"
authors: ["john-doe"]
---
data/authors.yml
john-doe:
name: "John Doe"
bio: "Creator of things."
avatar: "/images/john.jpg"
Usage in Templates
The site.authors object contains all authors found on the site. Each author object has:
key: The author ID (e.g., "john-doe")name: The author name (from data or ID fallback)pages: List of pages by this author (sorted by date)- Any custom fields from
data/authors.yml
{% for id, author in site.authors %}
<div class="author">
<img src="{{ author.avatar }}" alt="{{ author.name }}">
<h3>{{ author.name }}</h3>
<p>{{ author.bio }}</p>
<h4>Recent Posts</h4>
<ul>
{% for p in author.pages %}
<li><a href="{{ p.url }}">{{ p.title }}</a></li>
{% endfor %}
</ul>
</div>
{% endfor %}
Example
<title>{{ site.title }}</title>
<link rel="canonical" href="{{ site.base_url }}{{ page.url }}">
Section
A directory with _index.md that groups related content.
Properties
| Property | Type | Description |
|---|---|---|
| section.title | String | Section title |
| section.description | String? | Section description |
| section.pages | Array<Page> | Pages in this section |
| section.pages_count | Int | Number of pages |
| section.list | String | Pre-rendered HTML list (section_list) |
| section.subsections | Array<Section> | Child sections |
| section.assets | Array<String> | Static files in section |
| section.page_template | String? | Default template for pages |
| section.paginate_path | String | Pagination URL pattern |
| section.redirect_to | String? | Redirect URL |
For the current section URL in section.html, use page.url.
Flat Aliases
| Variable | Equivalent |
|---|---|
| section_title | section.title |
| section_description | section.description |
| section_list | Pre-rendered HTML list of pages |
From Front Matter
| Property | Type | Default | Description |
|---|---|---|---|
| sort_by | String? | "date" | Sort by: date, weight, title |
| reverse | Bool? | false | Reverse sort order |
| paginate | Int? | — | Pages per page |
| transparent | Bool | false | Pass pages to parent |
| generate_feeds | Bool | false | Generate RSS feed |
Iterating Pages
{% for p in section.pages %}
<article>
<h2><a href="{{ p.url }}">{{ p.title }}</a></h2>
<time>{{ p.date }}</time>
{% if p.description %}
<p>{{ p.description }}</p>
{% endif %}
</article>
{% endfor %}
Iterating Subsections
{% for sub in section.subsections %}
<div class="category">
<a href="{{ sub.url }}">{{ sub.title }}</a>
<span>({{ sub.pages_count }} articles)</span>
</div>
{% endfor %}
Using section_list
For simple listings, use the pre-rendered HTML:
<ul>{{ section_list | safe }}</ul>
For custom markup, iterate section.pages directly:
<ul>
{% for p in section.pages %}
<li>
<a href="{{ p.url }}">{{ p.title }}</a>
{% if p.date %}<time>{{ p.date }}</time>{% endif %}
</li>
{% endfor %}
</ul>
Page
An individual content file (.md).
Core Properties
| Property | Type | Description |
|---|---|---|
| page.title | String | Page title |
| page.description | String? | Page description |
| page.url | String | Relative URL path |
| page.permalink | String? | Absolute URL with base_url |
| page.section | String | Parent section name |
| page.date | String? | Publication date (YYYY-MM-DD) |
| page.updated | String? | Last updated date |
| page.language | String | Effective language code |
| page.translations | Array<TranslationLink> | Language variants |
Rendered HTML content is available as the top-level content variable.
Metadata Properties
| Property | Type | Description |
|---|---|---|
| page.draft | Bool | Is draft |
| page.weight | Int | Sort weight |
| page.image | String? | Featured image path |
| page.authors | Array<String> | Author names |
| page.extra | Object | Custom front matter fields |
Computed Properties
| Property | Type | Description |
|---|---|---|
| page.word_count | Int | Word count |
| page.reading_time | Int | Reading time (minutes) |
| page.summary | String? | Content before <!-- more --> |
| page.assets | Array<String> | Static files in page bundle |
Boolean Flags
| Property | Type | Default | Description |
|---|---|---|---|
| page.toc | Bool | false | Show table of contents |
| page.render | Bool | true | Should render |
| page.is_index | Bool | — | Is index file |
| page.generated | Bool | false | Auto-generated page |
| page.in_sitemap | Bool | true | Include in sitemap |
| page.in_search_index | Bool | true | Include in search |
Navigation Properties
| Property | Type | Description |
|---|---|---|
| page.lower | Page? | Previous page in reading order |
| page.higher | Page? | Next page in reading order |
| page.ancestors | Array<Page> | Parent section chain |
| page.translations | Array<TranslationLink> | Language variants |
Custom Metadata
| Property | Type | Description |
|---|---|---|
| page.extra | Object | Custom front matter fields |
Flat Aliases
| Variable | Equivalent |
|---|---|
| page_title | page.title |
| page_description | page.description |
| page_url | page.url |
| page_section | page.section |
| page_date | page.date |
| page_image | page.image |
| page_summary | page.summary |
| page_word_count | page.word_count |
| page_reading_time | page.reading_time |
| page_permalink | page.permalink |
| page_authors | page.authors |
| page_weight | page.weight |
| page_language | page.language |
| page_translations | page.translations |
| taxonomy_name | Current taxonomy name (taxonomy pages) |
| taxonomy_term | Current taxonomy term (taxonomy term pages) |
| content | Rendered HTML content |
Navigation Objects
page.lower / page.higher
Navigation follows the flat reading order across the entire site, similar to mdBook or Docusaurus. Pages are ordered depth-first through the section tree: section index → section pages → subsections (recursive). Within each section, pages are sorted by the section's sort_by setting (weight, date, or title).
| Property | Type | Description |
|---|---|---|
| .title | String | Page title |
| .url | String | Page URL |
| .description | String? | Page description |
| .date | String? | Page date |
<nav class="post-nav">
{% if page.lower %}
<a href="{{ page.lower.url }}">← {{ page.lower.title }}</a>
{% endif %}
{% if page.higher %}
<a href="{{ page.higher.url }}">{{ page.higher.title }} →</a>
{% endif %}
</nav>
page.ancestors
Parent sections for breadcrumbs:
<nav class="breadcrumbs">
<a href="/">Home</a>
{% for ancestor in page.ancestors %}
/ <a href="{{ ancestor.url }}">{{ ancestor.title }}</a>
{% endfor %}
/ <span>{{ page.title }}</span>
</nav>
page.translations
| Property | Type | Description |
|---|---|---|
| .code | String | Language code (e.g., "en") |
| .url | String | Translated page URL |
| .title | String | Title in that language |
| .is_current | Bool | Current page's language |
| .is_default | Bool | Default language |
{% if page.translations %}
<nav class="lang-switcher">
{% for t in page.translations %}
{% if t.is_current %}
<span>{{ t.code | upper }}</span>
{% else %}
<a href="{{ t.url }}">{{ t.code | upper }}</a>
{% endif %}
{% endfor %}
</nav>
{% endif %}
Accessing page.extra
Custom metadata from front matter:
+++
title = "Review"
[extra]
rating = 4.5
featured = true
pros = ["Fast", "Reliable"]
+++
{% if page.extra.featured %}
<span class="badge">Featured</span>
{% endif %}
<div class="rating">{{ page.extra.rating }} / 5</div>
<ul>
{% for pro in page.extra.pros %}
<li>{{ pro }}</li>
{% endfor %}
</ul>
Time Variables
| Variable | Type | Description |
|---|---|---|
| current_year | Int | Current year (e.g., 2025) |
| current_date | String | Current date (YYYY-MM-DD) |
| current_datetime | String | Current datetime |
<footer>© {{ current_year }} {{ site.title }}</footer>
SEO Variables
Pre-rendered HTML (backward compatible):
| Variable | Description |
|---|---|
| og_tags | OpenGraph meta tags |
| twitter_tags | Twitter Card meta tags |
| og_all_tags | Both OG and Twitter tags |
| canonical_tag | Canonical link tag |
| hreflang_tags | Hreflang alternate link tags (multilingual) |
| pagination_seo_links | <link rel="prev/next"> tags |
<head>
{{ og_all_tags | safe }}
{{ canonical_tag | safe }}
{{ hreflang_tags | safe }}
{{ pagination_seo_links | safe }}
</head>
Structured data for custom meta tag markup:
| Property | Type | Description |
|---|---|---|
| seo.canonical_url | String | Full canonical URL (base_url + page URL) |
| seo.og_type | String | OpenGraph type (default: "article") |
| seo.og_image | String | Resolved absolute image URL |
| seo.twitter_card | String | Twitter card type (default: "summary_large_image") |
| seo.twitter_site | String | Twitter site handle |
| seo.twitter_creator | String | Twitter creator handle |
| seo.fb_app_id | String | Facebook App ID |
| seo.hreflang | Array | Same as page.translations |
Page title, description, URL, and image are available as page.title, page.description, page.url, page.image. The seo object provides computed values specific to SEO (resolved URLs, config values).
<head>
<link rel="canonical" href="{{ seo.canonical_url }}">
<meta property="og:title" content="{{ page.title }}">
<meta property="og:type" content="{{ seo.og_type }}">
<meta property="og:url" content="{{ seo.canonical_url }}">
{% if page.description %}
<meta property="og:description" content="{{ page.description }}">
{% endif %}
{% if seo.og_image %}
<meta property="og:image" content="{{ seo.og_image }}">
{% endif %}
<meta name="twitter:card" content="{{ seo.twitter_card }}">
{% if seo.twitter_site %}
<meta name="twitter:site" content="{{ seo.twitter_site }}">
{% endif %}
</head>
Asset Variables
Pre-rendered <link> and <script> tags for convenience. These are generated from your config.toml settings.
| Variable | Description |
|---|---|
| highlight_css | Syntax highlighting CSS <link> tag |
| highlight_js | Syntax highlighting JS <script> tag |
| highlight_tags | Both CSS and JS tags |
| auto_includes_css | Auto-included CSS <link> tags |
| auto_includes_js | Auto-included JS <script> tags |
| auto_includes | All auto-include tags |
<head>
{{ highlight_css | safe }}
{{ auto_includes_css | safe }}
</head>
<body>
...
{{ highlight_js | safe }}
{{ auto_includes_js | safe }}
</body>
Table of Contents
Only available when toc = true in front matter.
Pre-rendered HTML (backward compatible):
| Variable | Type | Description |
|---|---|---|
| toc | String | Generated TOC HTML |
| toc_obj.html | String | Same TOC HTML in object form |
{% if page.toc %}
<aside class="toc">
{{ toc | safe }}
</aside>
{% endif %}
Structured data for custom TOC markup:
| Property | Type | Description |
|---|---|---|
| toc_obj.headers | Array | Structured TOC header objects |
| toc_obj.headers[].level | Int | Heading level (2-6) |
| toc_obj.headers[].id | String | Anchor ID |
| toc_obj.headers[].title | String | Heading text |
| toc_obj.headers[].permalink | String | Full anchor permalink |
| toc_obj.headers[].children | Array | Nested child headers (same structure) |
{% if page.toc %}
<nav class="toc">
<ul>
{% for h in toc_obj.headers %}
<li>
<a href="{{ h.permalink }}">{{ h.title }}</a>
{% if h.children %}
<ul>
{% for child in h.children %}
<li><a href="{{ child.permalink }}">{{ child.title }}</a></li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
</nav>
{% endif %}
Taxonomy Variables
Available in taxonomy templates:
| Variable | Type | Description |
|---|---|---|
| taxonomy_name | String | Taxonomy name (e.g., "tags") |
| taxonomy_term | String | Current term name |
| taxonomy_terms | Array | All terms (in taxonomy.html) |
| taxonomy_pages | Array<Page> | Pages for term |
Type Reference
Quick Reference
| Type | Description |
|---|---|
| String | Text value |
| String? | Optional text (may be nil) |
| Int | Integer number |
| Bool | true/false |
| Array<T> | List of type T |
| Object | Key-value map |
Template Checking
{# Check for nil #}
{% if page.description %}...{% endif %}
{# Check for empty array #}
{% if page.authors %}...{% endif %}
{# Check for empty string #}
{% if page.description is present %}...{% endif %}
{# Default value #}
{{ page.description | default(value=site.description) }}
See Also
- Template Syntax — Jinja2 basics
- Functions — Data retrieval functions
- Filters — Value transformation