Configuration Reference
Accent CMS is configured through a config.yaml file in your project root. All fields have sensible defaults, so a minimal config works out of the box. CLI arguments override config values when both are provided.
Minimal Configuration
A working config.yaml needs very little:
site: name: My Website
Everything else uses defaults. See each section below for the full set of fields.
site
Site-level metadata available in templates via the site variable.
| Field | Type | Default | Description |
|---|---|---|---|
site.name | string | "My Website" | Site title displayed in templates |
site.description | string | "" | Site description for meta tags |
site.url | string | Base URL for absolute links (required for RSS feed, sitemap, and robots.txt Sitemap directive) | |
site.language | string | "en" | Default language code |
site.languages | list | [] | Supported language codes for i18n. When two or more entries are listed, the site becomes multilingual. The default language must be included. |
site: name: My Blog description: A personal blog about Rust and web development url: https://example.com language: en languages: [en, de, fr]
server
HTTP server bind settings.
| Field | Type | Default | Description |
|---|---|---|---|
server.address | string | "127.0.0.1" | Bind address. Use "0.0.0.0" to listen on all interfaces. |
server.port | integer | 4400 | Bind port |
server: address: "0.0.0.0" port: 8080
theme
Theme selection and directory settings.
| Field | Type | Default | Description |
|---|---|---|---|
theme.name | string | "default" | Active theme name (must match a directory under theme.directory) |
theme.directory | string | "./themes" | Base directory containing theme directories |
theme: name: default directory: ./themes
content
Content directory and permalink settings.
| Field | Type | Default | Description |
|---|---|---|---|
content.directory | string | "./content" | Root directory for site content |
content.md_extension_redirects | bool | true | Auto-redirect .md URLs (e.g. /blog.md) to their clean URL. Set false to suppress the per-page redirect stubs in accent build and the 301s in accent serve |
content.permalinks | map | {} | URL prefix-to-pattern map for date-based or custom URL structures |
content.permalinks
Permalink patterns transform page URLs based on frontmatter metadata. Each key is a URL prefix (section), and the value is a pattern string with tokens that expand at load time. Pages whose directory-derived URL starts with a configured prefix get their URL rewritten; all other pages are unaffected.
Section index pages (e.g., default.md at /blog) are never transformed – only child pages.
The pattern replaces the entire URL, it does not extend the prefix. A page
under /blog matched by /blog: "/:year/:month/:slug" becomes
/2026/01/welcome, not /blog/2026/01/welcome. Re-include the prefix with
the :section token when you want to keep it – that is what the examples
below do.
Available tokens:
| Token | Source | Example |
|---|---|---|
:year | 4-digit year from frontmatter date | 2026 |
:month | Zero-padded month from date | 01 |
:day | Zero-padded day from date | 25 |
:slug | Filename stem or frontmatter slug override | welcome |
:title | Slugified frontmatter title | welcome-to-accent |
:section | Parent section name from the prefix | blog |
Date tokens (:year, :month, :day) require a date field in frontmatter. If missing, the page keeps its directory-based URL and a warning is logged.
Frontmatter overrides:
slug– Overrides the:slugtoken value (e.g.,slug: hello-worldproduces/blog/2026/01/hello-worldfrom the pattern above, instead of using the filename).url– Bypasses permalink patterns entirely and sets an absolute URL (e.g.,url: /custom/path).
When a permalink pattern rewrites a page URL, the original directory-based URL automatically returns a 301 redirect to the new URL. In static builds, a small HTML redirect file is generated at the old path.
content: directory: ./content permalinks: /blog: "/:section/:year/:month/:slug" /events: "/:section/:year/:month/:day/:slug" /news: "/:section/:year/:slug"
content.md_extension_redirects
Accent computes clean URLs by stripping the .md extension, then auto-generates a redirect so a GitHub-style link (/blog.md) resolves to the clean URL (/blog) instead of returning 404. In accent serve this is a 301; in accent build it is a small meta-refresh stub carrying a canonical link to the clean URL (e.g. public/blog.md/index.html). The stubs are excluded from sitemap.xml and feed.xml, and the home page is never redirected.
This is useful for documentation sites whose internal links use .md suffixes. If your site only ever links to clean URLs, set the flag to false to skip the redirects entirely – no build stubs, and .md URLs return a normal 404:
content: md_extension_redirects: false
When Markdown View is enabled in HTML mode, .md URLs serve the raw markdown source regardless of this setting.
media
Media serving and image processing settings. Media processing runs in accent build and dev serve with no license; serving in production requires a Standard or Pro license.
| Field | Type | Default | Description |
|---|---|---|---|
media.directory | string | "./media" | Shared media library directory |
media.page_local | bool | true | Enable page-local media serving (files alongside content .md files) |
media.serve_at_page_url | bool | true | Also serve page-local media at the bare page URL (alias for /content-media/...) |
media.processing Free
On-demand image processing runs in dev serve with no license; serving in production requires a Standard or Pro license.
| Field | Type | Default | Description |
|---|---|---|---|
media.processing.enabled | bool | false | Enable image processing (resizing, format conversion) |
media.processing.jpeg_quality | integer | 85 | Default JPEG output quality (1-100) |
media.processing.webp_quality | integer | 80 | Default WebP output quality (1-100) |
media.processing.max_dimension | integer | 4096 | Maximum resize dimension in pixels |
media.processing.responsive_widths | list | [320, 640, 960, 1280, 1920] | Responsive image breakpoints in pixels |
media.cache
Cache for processed media files.
| Field | Type | Default | Description |
|---|---|---|---|
media.cache.directory | string | "./.media-cache" | Directory for processed media cache |
media.cache.max_size_mb | integer | 500 | Maximum cache size in megabytes |
media: directory: ./media page_local: true serve_at_page_url: true # alias /content-media/{path} at the bare page URL processing: enabled: true jpeg_quality: 85 webp_quality: 80 max_dimension: 4096 responsive_widths: [320, 640, 960, 1280, 1920] cache: directory: ./.media-cache max_size_mb: 500
Bare-URL alias for page-local media
When media.serve_at_page_url: true (the default), files placed next
to a page’s default.md / README.md / index.md are reachable at
two URLs:
- The canonical
/content-media/<page>/<file>route, and - The bare page URL:
/<page>/<file>.
Page URLs always win over the alias, so a sub-page at /blog/post/
shadows any media file that would resolve to the same URL. Markdown
files are never aliased – the .md/.markdown rejection from the
shared media route is reused. Sites that rely on 404s at bare media
URLs (CDN fallthrough, strict URL contracts) can opt out by setting
media.serve_at_page_url: false.
A single page can also opt its sibling files out of the alias via frontmatter without disabling it site-wide:
--- title: Internal Drafts media: expose: false ---
When media.expose: false is set on a page, the alias returns 404 for
any sibling file in that page’s directory; the file is still reachable
at /content-media/... for theme references.
dev
Development settings. All dev features are automatically disabled when accent serve --production is used.
| Field | Type | Default | Description |
|---|---|---|---|
dev.hot_reload | bool | true | Enable file watching for templates, content, theme config, and assets |
dev.browser_reload | bool | true | Enable browser auto-refresh via Server-Sent Events at /_dev/reload |
dev.debounce_ms | integer | 500 | Debounce delay in milliseconds before processing file changes |
dev.show_drafts | bool | false | Show pages with status: draft or status: review in frontmatter |
dev.debug | bool | false | Enable the ?acdbg debug panel for inspecting template context |
dev: hot_reload: true browser_reload: true debounce_ms: 100 show_drafts: false debug: true
cache
Page rendering cache settings.
| Field | Type | Default | Description |
|---|---|---|---|
cache.max_size_mb | integer | 50 | Maximum cache size in megabytes |
cache.ttl_seconds | integer | 3600 | Time-to-idle in seconds before cached entries expire |
cache.warming | boolean | false | Pre-render all pages at startup to eliminate cold-start latency |
cache: max_size_mb: 100 ttl_seconds: 7200 warming: true # recommended for production
http_headers
HTTP response header settings for caching and CORS. Applied to all responses via middleware.
http_headers.cache
Controls Cache-Control, ETag, and Vary headers per content type. In dev mode, all responses get Cache-Control: no-store regardless of these settings.
| Field | Type | Default | Description |
|---|---|---|---|
http_headers.cache.enabled | boolean | true | Enable cache headers in production |
http_headers.cache.defaults.pages | string | "public, max-age=300, s-maxage=3600" | Cache-Control for HTML pages |
http_headers.cache.defaults.assets | string | "public, max-age=31536000, immutable" | Cache-Control for CSS/JS/WASM |
http_headers.cache.defaults.media | string | "public, max-age=86400" | Cache-Control for images/media |
http_headers.cache.defaults.fonts | string | "public, max-age=31536000, immutable" | Cache-Control for font files |
http_headers.cache.defaults.feed | string | "public, max-age=3600" | Cache-Control for RSS/Atom feeds |
http_headers.cache.routes | array | [] | Per-route cache-control overrides (first match wins) |
http_headers.cors
CORS (Cross-Origin Resource Sharing) headers. Disabled by default. When enabled, supersedes the legacy api.cors setting.
| Field | Type | Default | Description |
|---|---|---|---|
http_headers.cors.enabled | boolean | false | Enable CORS headers globally |
http_headers.cors.allowed_origins | array | [] | Allowed origins (use ["*"] for any) |
http_headers.cors.allowed_methods | array | ["GET", "HEAD", "OPTIONS"] | Allowed HTTP methods |
http_headers.cors.allowed_headers | array | ["Content-Type", "Accept"] | Allowed request headers |
http_headers.cors.expose_headers | array | ["ETag", "Last-Modified"] | Response headers exposed to client |
http_headers.cors.max_age | integer | 86400 | Preflight cache duration in seconds |
http_headers.cors.routes | array | [] | Per-route CORS overrides (first match wins) |
http_headers: cache: defaults: pages: "public, max-age=600, s-maxage=3600" routes: - path: "/api/*" cache_control: "no-store" cors: enabled: true allowed_origins: - "https://example.com" - "https://cdn.example.com" routes: - path: "/api/*" allowed_origins: ["*"] allowed_methods: ["GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS"]
http_headers.security
Baseline security response headers, applied to every response. These harden the site against MIME sniffing, clickjacking, base-URI hijacking, and abuse of uploaded files (notably scriptable SVG). Enabled by default with safe values.
| Field | Type | Default | Description |
|---|---|---|---|
http_headers.security.enabled | boolean | true | Master switch for all security headers |
http_headers.security.content_type_options | boolean | true | Emit X-Content-Type-Options: nosniff |
http_headers.security.frame_options | string | null | "SAMEORIGIN" | X-Frame-Options value (null to omit) |
http_headers.security.referrer_policy | string | null | "strict-origin-when-cross-origin" | Referrer-Policy value (null to omit) |
http_headers.security.content_security_policy | string | null | "object-src 'none'; base-uri 'self'; frame-ancestors 'self'" | Baseline CSP for page responses |
http_headers.security.media_content_security_policy | string | null | "default-src 'none'; sandbox" | Strict CSP for served media responses |
The default page CSP deliberately carries no script-src or default-src, so it does not interfere with theme or admin inline scripts; it still blocks plugin/embed (object-src), base-tag hijacking (base-uri), and framing (frame-ancestors). Media responses additionally receive the strict media CSP, and an image/svg+xml response is served with Content-Disposition: attachment so a scriptable SVG downloads instead of rendering inline. A header is only set if a handler did not already set it.
http_headers: security: enabled: true frame_options: "SAMEORIGIN" referrer_policy: "strict-origin-when-cross-origin" content_security_policy: "object-src 'none'; base-uri 'self'; frame-ancestors 'self'" media_content_security_policy: "default-src 'none'; sandbox"
markdown
Toggles for individual pulldown-cmark markdown parser extensions. All extensions are enabled by default. Set any field to false to disable that extension.
| Field | Type | Default | Description |
|---|---|---|---|
markdown.tables | bool | true | GFM tables (| A | B | syntax) |
markdown.footnotes | bool | true | New-style footnotes ([^label]) |
markdown.strikethrough | bool | true | Strikethrough (~~text~~) |
markdown.tasklists | bool | true | Task lists (- [x] / - [ ]) |
markdown.heading_attributes | bool | true | Heading attributes (# Heading { .class #id }) |
markdown.wikilinks | bool | true | Wiki-style links ([[page]]) |
markdown.smart_punctuation | bool | true | Smart punctuation (curly quotes, -- en-dash, --- em-dash) |
markdown.math | bool | true | LaTeX-style math ($inline$, $$display$$) |
markdown.definition_lists | bool | true | Definition lists |
markdown.gfm | bool | true | Miscellaneous GFM extensions (currently: block-quote alerts like > [!NOTE]) |
markdown.superscript | bool | true | Superscript (^text^) |
markdown.subscript | bool | true | Subscript (~text~). When enabled, single-tilde is subscript; double-tilde remains strikethrough. |
markdown.metadata_blocks | bool | true | YAML (---) and pluses (+++) metadata block parsing |
markdown.locale | string | (unset) | Override the typographic locale used by smart-quote remapping. ISO 639-1 codes (de, fr, pl, sv, fi, cs, ja, zh, …). When unset, the page’s language (which falls back to site.language) is used. |
markdown: tables: true footnotes: true strikethrough: true tasklists: true heading_attributes: true wikilinks: true smart_punctuation: true math: true definition_lists: true gfm: true superscript: true subscript: true metadata_blocks: true locale: de # optional: forces German curly-quote conventions
markdown.smart_quotes
Deployment-level overrides to the canonical smart-quote spec. Lets a site flip inner-guillemet spacing or replace a locale’s quote characters wholesale without forking the spec JSON. An empty (or omitted) section is a no-op.
| Field | Type | Default | Description |
|---|---|---|---|
markdown.smart_quotes.inner_space.default | enum | (unset) | Site-wide inner-guillemet spacing override. One of none, nbsp, thin. Beats the spec’s per-locale value – set this to enforce one spacing rule across every locale. |
markdown.smart_quotes.inner_space.per_locale | map | (empty) | Per-locale exceptions. Keys are canonical BCP-47 tags (fr-FR, de-CH). Each entry overrides the spec’s value for that exact tag. |
markdown.smart_quotes.locales | map | (empty) | Wholesale quote-character replacement, keyed by canonical BCP-47 tag. Each entry must specify both double and single as { open, close } pairs. Other spec fields (inner spacing, nested-override behaviour, writing direction) are preserved. |
Resolution priority for inner spacing (highest first):
markdown.smart_quotes.inner_space.per_locale[<tag>]– exact canonical matchmarkdown.smart_quotes.inner_space.per_locale[<language>]– language-only fallbackmarkdown.smart_quotes.inner_space.default- Spec’s
locales[<tag>].innerSpace - Spec’s
innerSpace.globalDefault
The same exact -> language resolution applies to the wholesale locales map: a locales: { de: ... } entry covers de-CH, de-AT, and any other German variant.
Non-canonical YAML keys (de_ch, FR-fr) are rewritten into canonical BCP-47 form at config load. Two entries that canonicalise to the same form are rejected as a collision. Unknown locale tags are rejected with a clear error message; region-only fall-throughs are accepted (de-LI validates because de is in the spec).
markdown: smart_quotes: inner_space: default: thin # flip every locale to thin spaces per_locale: fr-FR: nbsp # but keep NBSP for fr-FR de-CH: none # and drop spacing from Swiss German locales: # Editorial-style override: Norwegian site that prefers curly # doubles over the spec's guillemets. nb: double: { open: "\u{201C}", close: "\u{201D}" } single: { open: "\u{2018}", close: "\u{2019}" }
Reload with pkill -HUP accent (or POST /_admin/reload) without restarting the server.
code
Syntax highlighting settings for fenced code blocks.
| Field | Type | Default | Description |
|---|---|---|---|
code.theme | string | "base16-ocean.dark" | Syntax highlighting theme name (uses syntect themes). Set to "" to disable highlighting. |
code: theme: "InspiredGitHub"
feed
RSS 2.0 feed generation. Only pages with a date field in frontmatter are included.
| Field | Type | Default | Description |
|---|---|---|---|
feed.enabled | bool | true | Enable RSS feed generation |
feed.limit | integer | 20 | Maximum number of entries in the feed |
feed.path | string | "/feed.xml" | URL path where the feed is served |
For absolute URLs in feed entries (required by the RSS specification), set site.url.
feed: enabled: true limit: 20 path: /feed.xml
search
Client-side search index generation. See the Search guide for usage details.
| Field | Type | Default | Description |
|---|---|---|---|
search.enabled | bool | true | Generate the search index |
search.min_word_length | integer | 2 | Minimum word length for indexing |
search.content_length | integer | 200 | Maximum characters of plain-text content stored per page |
search.exclude | list | [] | URL glob patterns to exclude (e.g., "/404", "/tags/*") |
search.fields
Field weights for relevance scoring. Higher values rank matches in that field higher.
| Field | Type | Default | Description |
|---|---|---|---|
search.fields.title | float | 3.0 | Weight for title matches |
search.fields.content | float | 1.0 | Weight for body content matches |
search.fields.tags | float | 2.0 | Weight for tag matches |
search.fields.lead | float | 1.5 | Weight for lead/excerpt matches |
search: enabled: true min_word_length: 2 content_length: 200 exclude: - "/404" - "/tags/*" fields: title: 3.0 content: 1.0 tags: 2.0 lead: 1.5
robots
robots.txt generation.
| Field | Type | Default | Description |
|---|---|---|---|
robots.enabled | bool | true | Enable robots.txt serving at /robots.txt |
robots.rules | string | (auto-generated) | Custom robots.txt rules. When omitted, default rules are generated. A Sitemap: directive is appended when site.url is set. |
robots: enabled: true rules: | User-agent: * Allow: / Disallow: /admin/
well_known
Serve files from a .well-known directory per RFC 8615. Useful for security.txt, ACME challenges, and protocol discovery.
| Field | Type | Default | Description |
|---|---|---|---|
well_known.enabled | bool | false | Enable .well-known file serving |
well_known.directory | string | "./.well-known" | Source directory for .well-known files |
well_known: enabled: true directory: ./.well-known
plugins Free
WASM plugin system. The plugin runtime works in accent build and dev serve with no license; serving plugins in production requires a Standard or Pro license.
| Field | Type | Default | Description |
|---|---|---|---|
plugins.enabled | bool | false | Enable plugin loading and execution |
plugins.directory | string | "./plugins" | Directory containing installed plugins |
plugins.registry_url | string | (built-in) | Plugin registry URL for accent plugin install |
plugins.fuel_limit | integer | WASM instruction budget per plugin invocation (omit for unlimited) | |
plugins.compilation_cache | string | Directory for Wasmtime compilation cache (speeds up repeated loads) | |
plugins.precompile | bool | false | AOT pre-compile plugins at load time for faster execution |
plugins.allowed_paths | map | {} | WASI filesystem paths exposed to plugins. Prefix values with ro: for read-only access. |
plugins: enabled: true directory: ./plugins fuel_limit: 1000000 compilation_cache: .cache/plugins precompile: true allowed_paths: /content: "ro:./content" /media: "ro:./media" /data: ./site/data
versioning
Versioned content trees for documentation or any content section. Each root maps a URL prefix to a set of version definitions with fallback chains.
| Field | Type | Default | Description |
|---|---|---|---|
versioning.roots | map | {} | Versioning roots keyed by URL prefix (e.g., /docs). Empty means versioning is disabled. |
versioning.roots.<prefix>.versions | ordered map | (required) | Version definitions. Iteration order determines dropdown display order. |
versioning.roots.<prefix>.versions.<id>.label | string | (required) | Human-readable label (e.g., "1.1 (Latest)") |
versioning.roots.<prefix>.versions.<id>.badge | string | Optional badge hint for templates (e.g., "latest", "lts") | |
versioning.roots.<prefix>.versions.<id>.fallback | string | Version ID to fall back to when a page is missing in this version | |
versioning.roots.<prefix>.default | string | (required) | Default version served when no version prefix is in the URL |
versioning.roots.<prefix>.redirects | map | {} | Deprecated version redirects (e.g., v0.9: v1.0) |
versioning: roots: /docs: versions: v1.0: label: "1.0 (LTS)" badge: lts v1.1: label: "1.1 (Latest)" badge: latest fallback: v1.0 default: v1.1 redirects: v0.9: v1.0
Multiple roots are supported. Each root operates independently with its own version set, default, and redirects. Circular fallback chains and missing fallback/redirect targets are detected at startup.
See the Content Workflow guide for setup instructions and folder structure.
Configuration Loading
Priority Order
Configuration values are resolved in this order (later overrides earlier):
- Built-in defaults – Hardcoded in Accent CMS
config.yaml– Or the file specified by--config- CLI arguments – Highest priority
Config-Relative Paths
When --config points to a file outside the current working directory, relative paths in the config file are resolved relative to the config file’s parent directory, not the working directory.
# Paths in /srv/mysite/config.yaml resolve relative to /srv/mysite/ accent serve --config /srv/mysite/config.yaml
Runtime Reload
On Unix systems, send SIGHUP to reload config.yaml without restarting:
pkill -HUP accent
If the new configuration is invalid, the server keeps running with the previous configuration. See the Getting Started guide for details.
Full Example
site: name: My Website description: A markdown-powered website url: https://example.com language: en server: address: "127.0.0.1" port: 4400 theme: name: default directory: ./themes content: directory: ./content permalinks: /blog: "/:section/:year/:month/:slug" media: directory: ./media page_local: true processing: enabled: true jpeg_quality: 85 webp_quality: 80 max_dimension: 4096 responsive_widths: [320, 640, 960, 1280, 1920] cache: directory: ./.media-cache max_size_mb: 500 dev: hot_reload: true browser_reload: true debounce_ms: 100 show_drafts: false debug: false cache: max_size_mb: 50 ttl_seconds: 3600 code: theme: "base16-ocean.dark" feed: enabled: true limit: 20 path: /feed.xml search: enabled: true content_length: 200 exclude: - "/404" - "/tags/*" robots: enabled: true well_known: enabled: false directory: ./.well-known plugins: enabled: false directory: ./plugins # versioning: # roots: # /docs: # versions: # v1.0: # label: "1.0 (LTS)" # badge: lts # v1.1: # label: "1.1 (Latest)" # badge: latest # fallback: v1.0 # default: v1.1