Examples
EN

Configuration

All site configuration lives in config.toml at the project root.

Unknown top-level keys are reported instead of silently ignored — a typo'd [markdonw] or titel = "…" would otherwise disable a feature with no feedback. Hwaro warns with a suggestion when one is close to a real key:

Unknown key 'markdonw' in config.toml — hwaro does not read it. Did you mean 'markdown'?

The check covers top-level keys only; keys nested inside a section are validated by that section's own loader.

Site Settings

title = "My Site"
description = "Site description for SEO"
base_url = "https://example.com"
Key Type Description
title string Site title
description string Site description
base_url string Production URL (no trailing slash)

Environment Variables

You can reference environment variables in config.toml. Values are substituted before TOML parsing.

base_url = "${SITE_URL}"
title = "$SITE_TITLE"
description = "${SITE_DESC:-My awesome site}"
Syntax Description
${VAR} Substitute with env var value
$VAR Same as above (bare form)
${VAR:-default} Use default if VAR is unset or empty

Missing variables without defaults are left as-is and produce a build warning. See Environment Variables for template usage.

Build Options

[build]
output_dir = "public"
drafts = false
parallel = true
cache = false
hooks.pre = ["npm install", "npx tsc"]
hooks.post = ["npm run minify"]
Key Type Default Description
output_dir string "public" Output directory
drafts bool false Include draft content
parallel bool true Parallel processing
cache bool false Enable build caching
template_deps bool true Track template dependencies so a template edit only rebuilds the pages that render it
hooks.pre array [] Commands to run before build
hooks.post array [] Commands to run after build

output_dir, drafts, parallel and cache each back a hwaro build flag, and the flag wins:

command-line flag  >  config.toml  >  built-in default

So hwaro build -o dist overrides output_dir in config, and hwaro build --drafts still includes drafts when drafts = false. The reverse does not exist — there is no --no-drafts or --no-cache to turn a config value back off, so set those keys only when you want them on for every build. --no-parallel is the one exception, and it overrides parallel = true.

These apply to hwaro serve as well, which builds into (and serves from) output_dir. hwaro deploy follows it too: when [deployment] source_dir is not set, it deploys output_dir rather than assuming public.

output_dir may be relative to the project or absolute. A path that escapes the project (../out) is refused, because the dev server will not serve from outside the project and the build would otherwise write somewhere serve never reads.

See Build Hooks for error handling and use cases.

Markdown

[markdown]
safe = false
lazy_loading = false
emoji = false
footnotes = true
task_lists = true
definition_lists = true
mermaid = false
math = false
math_engine = "katex"
Key Type Default Description
safe bool false Strip raw HTML from markdown
lazy_loading bool false Automatically add loading="lazy" to images
emoji bool false Convert emoji shortcodes (e.g. :smile:) to emoji characters
footnotes bool true Enable footnote syntax ([^1])
task_lists bool true Enable task list syntax (- [ ] / - [x])
task_list_classes bool false Add GFM classes (task-list-item, contains-task-list) to task-list markup
definition_lists bool true Enable definition list syntax (Term\n: Definition)
mermaid bool false Render `mermaid blocks as <div class="mermaid">
math bool false Enable math syntax ($...$ and $$...$$)
math_engine string "katex" Math rendering engine ("katex" or "mathjax")
smart_punctuation bool false Typographic quotes/dashes/ellipses ("x" → “x”, -- → –, ... → …)
containers bool false :::note Title::: custom containers (admonition markup)
insert_anchor_links string "none" Site-wide heading anchor links: "none", "left", or "right" (page front matter overrides)
external_links_target_blank bool false Add target="_blank" rel="noopener" to absolute http(s) links
external_links_no_follow bool false Add rel="nofollow" to absolute http(s) links
external_links_no_referrer bool false Add rel="noreferrer" to absolute http(s) links

See Markdown Extensions for syntax details and examples.

Rewrite content directory paths to custom URL paths. Useful for site restructuring without breaking links.

[permalinks]
"old/posts" = "posts"
"2023/drafts" = "archive/2023"
Source (Directory) Target (URL Path) Example Effect
content/old/posts/a.md posts/ /old/posts/a/ -> /posts/a/

Rules are evaluated in declaration order and the first source that matches the page's directory (exactly or as a parent prefix) wins — later rules are never consulted for that page. Declare specific prefixes before broad ones ("posts/tech" before "posts"), or the broad rule shadows the specific one. This applies to token patterns too, and especially to the "" catch-all: put it last, after every other rule.

Token patterns

A target containing :token segments is a Hugo-style pattern that rebuilds the whole URL instead of remapping the directory:

[permalinks]
"posts" = "/:year/:month/:day/:slug/"

With content/posts/hello.md dated 2026-03-05, the page is published at /2026/03/05/hello/.

Token Expands to
:year Page date year (2026)
:month Page date month, zero-padded (03)
:day Page date day, zero-padded (05)
:slug Front-matter slug, or the filename stem when unset
:title Slugified front-matter title (falls back to :slug when it slugifies to nothing)
:section The page's section path (posts/tech); empty for root pages, collapsing the segment
:filename The filename stem, ignoring any slug override

Notes:

Control how unresolved @/path.md internal links are treated during the build.

[links]
broken_internal = "error"
Key Type Default Description
broken_internal string "warn" "warn" logs each unresolved @/ link and keeps the raw markup; "error" fails the build (exit code 5) with one aggregated list of every offender

See Internal Links for the @/ link syntax and the --cache caveat in strict mode.

Taxonomies

[[taxonomies]]
name = "tags"
feed = true
paginate_by = 10

[[taxonomies]]
name = "categories"
feed = true
Key Type Default Description
name string Taxonomy name (used in front matter)
feed bool false Generate RSS feed for each term
sitemap bool true Include taxonomy pages in sitemap
paginate_by int Items per page on term pages
sort_by string "date" Order of pages within a term: "date" (newest first), "title", or "weight"
reverse bool false Flip whichever order sort_by produced
terms_sort_by string "name" Order of the terms list on the taxonomy index: "name" or "count"

Term feeds stay reverse-chronological regardless of sort_by. See Taxonomies for the full sorting rules.

Named navigation menus, rendered in templates via site.menus / get_menu().

[[menus.main]]
name = "Posts"
url = "/posts/"
weight = 1

[[menus.main]]
name = "About"
url = "/about/"
weight = 2
Key Type Default Description
name string Required. Entry skipped (with a warning) if missing.
url string "" Root-relative or absolute http(s):///// URL.
weight int 0 Sort order within the menu.
identifier string name Unique key other entries reference via parent.
parent string none Nest this entry under another entry's identifier.

Pages/sections can also join a menu from their own front matter (menus = ["main"]) without touching this file. A [languages.<code>] block with no menus table inherits this global set; declaring [[languages.<code>.menus.<name>]] replaces it for that language. See Menus for the full reference (hierarchy, per-language behavior, active_path styling).

Static Files

Everything under static/ is copied verbatim into the site root, preserving its directory structure — static/css/app.css is served at /css/app.css. Hidden entries are included too, so static/.well-known/security.txt is published at /.well-known/security.txt. By default Hwaro filters out common OS, editor, and VCS cruft so it never ships to production.

[static]
use_default_excludes = true              # filter built-in cruft (default)
exclude = ["*.bak", "drafts/**"]         # extra patterns to skip
Key Type Default Description
use_default_excludes bool true Filter the built-in cruft denylist (.DS_Store, Thumbs.db, desktop.ini, .git, vim swap files, …)
exclude array [] Extra patterns to skip. A glob like *.bak matches at any depth, drafts/** scopes a subtree, and a literal name is anchored to an exact file or directory (drafts drops drafts/…)

The built-in denylist only removes cruft — legitimate dot-paths such as .well-known/ and .domains are never filtered and are always published, identically for cold and --cache/incremental builds. Set use_default_excludes = false to disable the built-in filtering entirely.

Development Server

Options for hwaro serve only — they never affect hwaro build output.

[serve]
fast = true                          # always serve in fast dev mode

[serve.headers]
X-Frame-Options = "SAMEORIGIN"
Cache-Control = "no-store"
Key Type Default Description
fast bool false Serve as if --fast was passed (skips OG image generation and image processing); explicit CLI skip flags still apply
headers table {} Custom HTTP response headers added to every dev-server response; CLI --header values win on duplicate keys

See the serve command for the matching CLI flags.

Feature Configuration Reference

Each feature has its own documentation with full configuration details. Below is a quick reference of all config.toml sections.

Config Section Documentation Description
[feeds] SEO RSS/Atom feed generation
[sitemap] SEO Sitemap XML generation
[robots] SEO Robots.txt generation
[og] SEO OpenGraph & Twitter Card meta tags
[og.auto_image] Auto OG Images Auto-generate OG preview images (including lazy_generate for fast dev server)
[search] Search Client-side search index
[highlight] Syntax Highlighting Code syntax highlighting
[pagination] Pagination Section pagination
[auto_includes] Auto Includes Auto-include CSS/JS files
[assets] Asset Pipeline CSS/JS minification & fingerprinting
[sass] Sass/SCSS Built-in SCSS compilation (pure Crystal)
[image_processing] Image Processing Image resizing & LQIP
[image_processing.lqip] Image Processing Base64 blur-up placeholders
[content.files] Content Files Publish non-Markdown files
[static] Static Files Filter cruft / exclude paths from the static/ copy
[serve] Development Server Dev-server response headers & fast mode
[links] Links Broken internal @/ link handling (warn or fail the build)
[series] Series Group posts into ordered series
[related] Related Posts Related content recommendations
[llms] LLMs.txt AI/LLM crawler instructions
[pwa] PWA Progressive Web App support
[amp] AMP Accelerated Mobile Pages
[deployment] Deploy Deploy targets configuration
[doctor] Doctor Suppress known diagnostic issues
languages.* Multilingual Multi-language support
[[menus.*]] Menus Named navigation menus

Plugins

[plugins]
processors = ["markdown"]

Full Example

A complete config.toml with all core sections. Copy and adjust to your needs.

title = "My Blog"
description = "A blog about programming"
base_url = "https://myblog.com"
default_language = "en"

[build]
output_dir = "public"
drafts = false
parallel = true
cache = false
hooks.pre = ["npm ci"]
hooks.post = ["npm run optimize"]

[markdown]
safe = false
lazy_loading = true
emoji = false
footnotes = true
task_lists = true

[permalinks]
"old/posts" = "posts"
"posts" = "/:year/:month/:day/:slug/"

[plugins]
processors = ["markdown"]

[[taxonomies]]
name = "tags"
feed = true

[[taxonomies]]
name = "categories"

[[menus.main]]
name = "Posts"
url = "/posts/"

# Feature sections — see Feature Configuration Reference above
# [feeds], [sitemap], [robots], [og], [search], [highlight],
# [pagination], [auto_includes], [assets], [sass], [image_processing],
# [series], [related], [llms], [pwa], [amp], [deployment], etc.

See Also