Pages
Pages are Markdown files that become HTML pages on your site. This page covers how to write content — front matter fields, Markdown syntax, and file organization. For how these fields are accessed in templates, see the Data Model.
Basic Structure
+++
title = "My Page"
date = "2024-01-15"
+++
Your content in **Markdown**.
The +++ block is TOML front matter. YAML (--- delimiters) and JSON (a top-level {...} object at the start of the file) are also supported. Content below becomes HTML.
---
title: "My Page"
date: "2024-01-15"
---
Your content in **Markdown**.
{
"title": "My Page",
"date": "2024-01-15"
}
Your content in **Markdown**.
For JSON, the first balanced {...} at the very start of the file is the front matter — no fence is needed. The file must begin with { (no leading whitespace).
Front Matter
Required
| Field | Type | Description |
|---|---|---|
| title | string | Page title |
Common Fields
| Field | Type | Default | Description |
|---|---|---|---|
| date | string | — | Publication date (YYYY-MM-DD) |
| description | string | — | SEO description |
| draft | bool | false | Exclude from production builds |
| template | string | "page" | Template to use |
| weight | int | 0 | Sort order (lower = first) |
| image | string | — | Featured image for social sharing |
| tags | array | [] | Tag taxonomy terms |
| categories | array | [] | Category taxonomy terms |
All Fields
| Field | Type | Description |
|---|---|---|
| updated | string | Last updated date |
| slug | string | Custom URL slug |
| path | string | Custom URL path |
| aliases | array | Redirect URLs to this page |
| authors | array | Author names |
| toc | bool | Show table of contents |
| in_search_index | bool | Include in search |
| in_sitemap | bool | Include in sitemap |
| insert_anchor_links | bool | Add heading anchors |
| redirect_to | string | Redirect page to this URL |
| render | bool | Render page to output (default: true) |
| expires | date | Auto-exclude after this date |
| series | string | Series name for grouping |
| series_weight | int | Sort order within series |
| extra | table | Custom metadata |
Examples
Blog Post
+++
title = "Getting Started with Crystal"
date = "2024-01-15"
description = "Learn Crystal programming basics"
tags = ["crystal", "tutorial"]
authors = ["Alice Smith"]
image = "/images/crystal-guide.png"
+++
Crystal is a fast, compiled language...
Draft
+++
title = "Work in Progress"
draft = true
+++
Not visible in production.
Build with drafts: hwaro build --drafts
Expiring Content
+++
title = "Limited Time Offer"
expires = 2025-12-31
+++
Automatically excluded from builds after the expiry date.
Build with expired content: hwaro build --include-expired
Pages expiring within 7 days generate a build warning.
Future-Dated Content
Pages with a date in the future are automatically excluded from builds. This is useful for scheduling content.
+++
title = "Coming Soon"
date = 2099-01-01
+++
Published only after the date arrives.
Build with future content: hwaro build --include-future
Series Post
+++
title = "Part 1: Introduction"
series = "Crystal Tutorial"
series_weight = 1
+++
First part of the series.
In templates, access page.series, page.series_index, and page.series_pages.
Custom Template
+++
title = "Landing Page"
template = "landing"
+++
Uses `templates/landing.html` instead of `page.html`.
Weighted Order
+++
title = "Introduction"
weight = 1
+++
+++
title = "Getting Started"
weight = 2
+++
Lower weight appears first.
URL Aliases
+++
title = "New Page"
aliases = ["/old-url/", "/another-old-url/"]
+++
Redirects from old URLs to this page.
Custom Metadata
+++
title = "Product Review"
[extra]
rating = 4.5
featured = true
pros = ["Fast", "Reliable"]
+++
Access in templates: {{ page.extra.rating }}
Full Front Matter Reference
All available fields in one block. Copy and remove what you don't need.
+++
title = "Page Title"
date = "2024-01-15"
updated = "2024-02-01"
description = "SEO description"
draft = false
template = "page"
weight = 0
slug = "custom-slug"
path = "custom/path"
aliases = ["/old-url/"]
image = "/images/cover.png"
tags = ["tag1", "tag2"]
categories = ["category1"]
authors = ["Author Name"]
toc = true
in_search_index = true
in_sitemap = true
insert_anchor_links = true
render = true
redirect_to = ""
expires = 2025-12-31
series = "Series Name"
series_weight = 1
[extra]
custom_field = "value"
+++
Content Summary
Use <!-- more --> to define a summary:
+++
title = "Long Article"
+++
This is the summary shown in listings.
<!-- more -->
The full article continues here...
Markdown Syntax
Text
**bold** and *italic*
`inline code`
[link](https://example.com)

Lists
- Unordered
- Items
1. Ordered
2. Items
Code Blocks
```javascript
console.log("Hello");
```
Tables
<table>
<thead>
<tr>
<th>Header</th>
<th>Header</th>
</tr>
</thead>
<tbody>
<tr>
<td>Cell</td>
<td>Cell</td>
</tr>
</tbody>
</table>
Table cells support inline Markdown: bold, italic, code spans, links, , and
strikethrough.
<table>
<thead>
<tr>
<th>Feature</th>
<th>Example</th>
</tr>
</thead>
<tbody>
<tr>
<td>Bold</td>
<td><strong>important</strong></td>
</tr>
<tr>
<td>Italic</td>
<td><em>emphasis</em></td>
</tr>
<tr>
<td>Code</td>
<td><code>config.toml</code></td>
</tr>
<tr>
<td>Link</td>
<td><a href="https://example.com">Hwaro</a></td>
</tr>
<tr>
<td>Image</td>
<td><img src="/img/logo.png" alt="logo"></td>
</tr>
<tr>
<td>Strikethrough</td>
<td><del>deprecated</del></td>
</tr>
</tbody>
</table>
Internal Links
Use @/ to link to other content pages by their source path. Hwaro resolves these to the correct output URL at build time.
[Read the post](@/blog/my-post.md)
[About section](@/about/_index.md)
[With anchor](@/blog/my-post.md#introduction)
This is useful because you don't need to know the final URL — Hwaro calculates it from the content path. If the target page doesn't exist, the link is left unchanged and a warning is logged during build.
| Syntax | Resolved URL |
|---|---|
@/blog/post.md |
/blog/post/ |
@/blog/_index.md |
/blog/ |
@/blog/post.md#section |
/blog/post/#section |
Blockquotes
> Quote text
Admonitions
GitHub-style alert blocks render as styled callouts. Recognised types: NOTE, TIP, IMPORTANT, WARNING, CAUTION.
> [!NOTE]
> Pay attention to this paragraph.
> [!WARNING]
>
> Body can also live in its own paragraph.
The output is a <div class="admonition admonition-{type}"> with a title paragraph (<p class="admonition-title">) followed by the body. Style it from your CSS — Hwaro emits semantic markup only.
Disable by setting admonitions = false under [markdown] in config.toml.
Limitations: matching is type-case-sensitive ([!NOTE] only, not [!note]), and a nested blockquote inside an admonition body closes the outer admonition early. There is no inline escape — backslash-escaping (\[!NOTE\]) renders the same characters and still triggers the admonition, so disable the feature if you need to render the literal token.
Custom Heading IDs
Append {#custom-id} to a heading line to override the auto-generated slug. Useful when you want stable anchor URLs that don't break on title edits.
## Installation Guide {#install}
Renders as <h2 id="install">Installation Guide</h2>. The TOC and any [link](#install) will use the custom id.
Allowed id characters: letters, digits, _, -, :. The id must start with a letter. CommonMark allows up to 3 leading spaces before an ATX heading; deeper indentation makes the line a code block, in which case {#id} is not applied.
Disable by setting heading_ids = false under [markdown] in config.toml.
Custom heading IDs require markdown.safe = false. Under safe mode the {#id} syntax is stripped from the rendered output and no id is applied — use raw HTML headings if you need both safe mode and explicit ids. Writing the same {#id} twice in one page produces duplicate id attributes; the first anchor wins.
Definition Lists
<dl>
<dt>Term</dt>
<dd>Definition body</dd>
<dt>Another term</dt>
<dd>Definition with <strong>bold</strong>, <em>italic</em>, <code>code</code>, <a href="https://example.com">a link</a>, and <del>strikethrough</del></dd>
<dd>A second definition for the same term</dd>
</dl>
Inline Markdown works inside both terms and definitions. Raw HTML is escaped for safety.
Asset Colocation
You can keep related assets (images, PDFs, etc.) in the same directory as your content file. This is known as a Page Bundle.
To use this feature, rename your markdown file to index.md (for regular pages) or _index.md (for section pages) and place it in a directory named after your page.
Example Structure:
content/
└── blog/
├── my-trip/
│ ├── index.md <-- The page content
│ ├── photo.jpg <-- Asset
│ └── data.json <-- Asset
└── _index.md
Hwaro will copy all non-markdown files from the page bundle directory to the output directory, maintaining the relative path.
In your markdown, you can link to these assets using relative paths:

[Download Data](data.json)
Accessing Assets in Templates
You can access the list of colocated assets in your templates using page.assets. This returns an array of relative paths to the files.
{% for asset in page.assets %}
{% if asset is matching("[.](jpg|png)$") %}
<img src="{{ get_url(path=asset) }}" alt="Gallery Image">
{% endif %}
{% endfor %}
URL Mapping
| File | URL |
|---|---|
| content/index.md | / |
| content/about.md | /about/ |
| content/blog/post.md | /blog/post/ |
See Also
- Sections — Group related pages
- Data Model — Page properties in templates