Examples
EN

Configuration

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

Any option documented as a list of strings also accepts a single string, which is read as a one-item list: sections = "posts" means sections = ["posts"]. An empty string, such as exclude = "${SITEMAP_EXCLUDE:-}" with the variable unset, counts as not set and keeps the default. A value that is neither a string nor a list (fields = true) is ignored with a warning.

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: a misspelled key nested inside a section ([markdown] emoij = true) is not reported, so double-check nested keys against the tables below. A known section written in the wrong shape, such as highlight = false instead of a [highlight] table or taxonomies = ["tags"] instead of [[taxonomies]] entries, is ignored with a warning as well. sitemap = true is the one short form still accepted: it means [sitemap] enabled = true.

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)
default_language string Language code of unsuffixed content files (default "en"). See Multilingual

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
write_stats bool false Write hwaro_stats.json (tags, classes and ids used by the rendered pages) at the project root for Tailwind and similar tools. See Asset Pipeline
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.

drafts, parallel and cache apply to hwaro serve as well. output_dir does not: serve always builds into its own .hwaro/serve/ and leaves output_dir untouched (see the serve command). hwaro deploy does follow output_dir: 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 relative path that escapes the project (../out) or an empty value is ignored with a warning, and the default public is used instead.

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")
admonitions bool true GitHub-style > [!NOTE] blockquotes become admonition blocks
heading_ids bool true Custom heading IDs (## Heading {#custom-id})
ins bool false Inserted text (++text++ → <ins>text</ins>)
mark bool false Highlighted text (==text== → <mark>text</mark>)
sub bool false Subscript (~text~ → <sub>text</sub>)
sup bool false Superscript (^text^ → <sup>text</sup>)
attributes bool false Generalized {#id .class key=val} blocks on headings and inline images
smart_punctuation bool false Typographic quotes/dashes/ellipses ("x" → “x”, -- → –, ... → …)
containers bool false :::note Title … ::: custom containers (admonition markup)
wikilinks bool false Obsidian syntax: [[wikilinks]], ![[image]] embeds, ![[note]] transclusion and foldable > [!type]- callouts (see Wikilinks & Backlinks)
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.

Content

[content]
summary_length = 70
summary_ellipsis = "…"
backlinks = false
Key Type Default Description
summary_length int 70 Automatic summary length for pages without <!-- more --> or description: words for space-delimited text, characters ×2 for CJK-dominant text. 0 disables the automatic summary
summary_ellipsis string "…" Appended to an automatic summary only when text was cut
backlinks bool false Fill page.backlinks with the pages linking to each page (see Wikilinks & Backlinks)

See Content Summary for how page.summary is chosen. [content.files], [content.new], [[content.generate]] and [[content.schema]] are documented in the feature reference below.

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"
broken_anchors = "warn"
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
broken_anchors string "ignore" "warn" or "error" checks @/page.md#id and same-page #id links in page content against the id/name attributes in the target's rendered HTML (headings, {#id}, shortcodes, templates, footnotes); "error" fails the build listing source → link → missing id. "ignore" skips the check

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

The anchor check reads each target page's output file after the render phase, so on --cache and serve rebuilds a target that was not re-rendered is checked as last written (no false positives). Only links in pages rendered in that pass are checked; run a cold build in CI for a complete check.

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), "published" (newest first, ignoring updated), "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).

To fill a menu from the top-level sections automatically (Hugo's sectionPagesMenu), name it under [menus]:

[menus]
auto_sections = "main"
Key Type Default Description
auto_sections string none (off) Menu that every top-level section joins (identifier = directory name, name = title, url, weight). Draft, unpublished, headless, transparent and off-site redirect_to sections are skipped, as is a directory without _index.md; an explicit entry with the same identifier wins. Reserved key: a non-string or blank value is a config error. See Automatic Section Menus.

Static Files

Everything under static/ is copied verbatim into the site root, preserving its directory structure, so 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/JSON Feed generation
[sitemap] SEO Sitemap XML generation
[robots] SEO Robots.txt generation (including Content-Signal lines)
[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, Subresource Integrity (sri)
[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
[content.new] Archetypes Default front matter and bundle style for hwaro new
[[content.generate]] Content Generation Materialize site.data records into pages
[[content.schema]] Front Matter Schema Per-section front-matter types, required fields, enums, bounds and defaults
[[data.remote]] Remote Data Sources Fetch remote data into site.data at build time
[outputs] Output Formats Extra per-page/per-section output formats (JSON, XML, TXT, CSV)
[privacy] Privacy Mode Self-host third-party fonts, scripts and images at build time
[csp] Content Security Policy Content-Security-Policy from inline script/style hashes (_headers or <meta>)
[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
[git] Git Metadata page.git commit info and updated/date fallbacks from history
[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
[versions] / [[versions.list]] Versioned Docs Several documentation versions side by side
[[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], [git], [llms], [pwa], [amp], [deployment], etc.

See Also