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.

FieldTypeDefaultDescription
site.namestring"My Website"Site title displayed in templates
site.descriptionstring""Site description for meta tags
site.urlstringBase URL for absolute links (required for RSS feed, sitemap, and robots.txt Sitemap directive)
site.languagestring"en"Default language code
site.languageslist[]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.

FieldTypeDefaultDescription
server.addressstring"127.0.0.1"Bind address. Use "0.0.0.0" to listen on all interfaces.
server.portinteger4400Bind port
server:
  address: "0.0.0.0"
  port: 8080

theme

Theme selection and directory settings.

FieldTypeDefaultDescription
theme.namestring"default"Active theme name (must match a directory under theme.directory)
theme.directorystring"./themes"Base directory containing theme directories
theme:
  name: default
  directory: ./themes

content

Content directory and permalink settings.

FieldTypeDefaultDescription
content.directorystring"./content"Root directory for site content
content.md_extension_redirectsbooltrueAuto-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.permalinksmap{}URL prefix-to-pattern map for date-based or custom URL structures

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:

TokenSourceExample
:year4-digit year from frontmatter date2026
:monthZero-padded month from date01
:dayZero-padded day from date25
:slugFilename stem or frontmatter slug overridewelcome
:titleSlugified frontmatter titlewelcome-to-accent
:sectionParent section name from the prefixblog

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 :slug token value (e.g., slug: hello-world produces /blog/2026/01/hello-world from 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.

FieldTypeDefaultDescription
media.directorystring"./media"Shared media library directory
media.page_localbooltrueEnable page-local media serving (files alongside content .md files)
media.serve_at_page_urlbooltrueAlso 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.

FieldTypeDefaultDescription
media.processing.enabledboolfalseEnable image processing (resizing, format conversion)
media.processing.jpeg_qualityinteger85Default JPEG output quality (1-100)
media.processing.webp_qualityinteger80Default WebP output quality (1-100)
media.processing.max_dimensioninteger4096Maximum resize dimension in pixels
media.processing.responsive_widthslist[320, 640, 960, 1280, 1920]Responsive image breakpoints in pixels

media.cache

Cache for processed media files.

FieldTypeDefaultDescription
media.cache.directorystring"./.media-cache"Directory for processed media cache
media.cache.max_size_mbinteger500Maximum 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.

FieldTypeDefaultDescription
dev.hot_reloadbooltrueEnable file watching for templates, content, theme config, and assets
dev.browser_reloadbooltrueEnable browser auto-refresh via Server-Sent Events at /_dev/reload
dev.debounce_msinteger500Debounce delay in milliseconds before processing file changes
dev.show_draftsboolfalseShow pages with status: draft or status: review in frontmatter
dev.debugboolfalseEnable 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.

FieldTypeDefaultDescription
cache.max_size_mbinteger50Maximum cache size in megabytes
cache.ttl_secondsinteger3600Time-to-idle in seconds before cached entries expire
cache.warmingbooleanfalsePre-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.

FieldTypeDefaultDescription
http_headers.cache.enabledbooleantrueEnable cache headers in production
http_headers.cache.defaults.pagesstring"public, max-age=300, s-maxage=3600"Cache-Control for HTML pages
http_headers.cache.defaults.assetsstring"public, max-age=31536000, immutable"Cache-Control for CSS/JS/WASM
http_headers.cache.defaults.mediastring"public, max-age=86400"Cache-Control for images/media
http_headers.cache.defaults.fontsstring"public, max-age=31536000, immutable"Cache-Control for font files
http_headers.cache.defaults.feedstring"public, max-age=3600"Cache-Control for RSS/Atom feeds
http_headers.cache.routesarray[]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.

FieldTypeDefaultDescription
http_headers.cors.enabledbooleanfalseEnable CORS headers globally
http_headers.cors.allowed_originsarray[]Allowed origins (use ["*"] for any)
http_headers.cors.allowed_methodsarray["GET", "HEAD", "OPTIONS"]Allowed HTTP methods
http_headers.cors.allowed_headersarray["Content-Type", "Accept"]Allowed request headers
http_headers.cors.expose_headersarray["ETag", "Last-Modified"]Response headers exposed to client
http_headers.cors.max_ageinteger86400Preflight cache duration in seconds
http_headers.cors.routesarray[]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.

FieldTypeDefaultDescription
http_headers.security.enabledbooleantrueMaster switch for all security headers
http_headers.security.content_type_optionsbooleantrueEmit X-Content-Type-Options: nosniff
http_headers.security.frame_optionsstring | null"SAMEORIGIN"X-Frame-Options value (null to omit)
http_headers.security.referrer_policystring | null"strict-origin-when-cross-origin"Referrer-Policy value (null to omit)
http_headers.security.content_security_policystring | null"object-src 'none'; base-uri 'self'; frame-ancestors 'self'"Baseline CSP for page responses
http_headers.security.media_content_security_policystring | 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.

FieldTypeDefaultDescription
markdown.tablesbooltrueGFM tables (| A | B | syntax)
markdown.footnotesbooltrueNew-style footnotes ([^label])
markdown.strikethroughbooltrueStrikethrough (~~text~~)
markdown.tasklistsbooltrueTask lists (- [x] / - [ ])
markdown.heading_attributesbooltrueHeading attributes (# Heading { .class #id })
markdown.wikilinksbooltrueWiki-style links ([[page]])
markdown.smart_punctuationbooltrueSmart punctuation (curly quotes, -- en-dash, --- em-dash)
markdown.mathbooltrueLaTeX-style math ($inline$, $$display$$)
markdown.definition_listsbooltrueDefinition lists
markdown.gfmbooltrueMiscellaneous GFM extensions (currently: block-quote alerts like > [!NOTE])
markdown.superscriptbooltrueSuperscript (^text^)
markdown.subscriptbooltrueSubscript (~text~). When enabled, single-tilde is subscript; double-tilde remains strikethrough.
markdown.metadata_blocksbooltrueYAML (---) and pluses (+++) metadata block parsing
markdown.localestring(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.

FieldTypeDefaultDescription
markdown.smart_quotes.inner_space.defaultenum(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_localemap(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.localesmap(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):

  1. markdown.smart_quotes.inner_space.per_locale[<tag>] – exact canonical match
  2. markdown.smart_quotes.inner_space.per_locale[<language>] – language-only fallback
  3. markdown.smart_quotes.inner_space.default
  4. Spec’s locales[<tag>].innerSpace
  5. 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.

FieldTypeDefaultDescription
code.themestring"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.

FieldTypeDefaultDescription
feed.enabledbooltrueEnable RSS feed generation
feed.limitinteger20Maximum number of entries in the feed
feed.pathstring"/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

Client-side search index generation. See the Search guide for usage details.

FieldTypeDefaultDescription
search.enabledbooltrueGenerate the search index
search.min_word_lengthinteger2Minimum word length for indexing
search.content_lengthinteger200Maximum characters of plain-text content stored per page
search.excludelist[]URL glob patterns to exclude (e.g., "/404", "/tags/*")

search.fields

Field weights for relevance scoring. Higher values rank matches in that field higher.

FieldTypeDefaultDescription
search.fields.titlefloat3.0Weight for title matches
search.fields.contentfloat1.0Weight for body content matches
search.fields.tagsfloat2.0Weight for tag matches
search.fields.leadfloat1.5Weight 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.

FieldTypeDefaultDescription
robots.enabledbooltrueEnable robots.txt serving at /robots.txt
robots.rulesstring(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.

FieldTypeDefaultDescription
well_known.enabledboolfalseEnable .well-known file serving
well_known.directorystring"./.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.

FieldTypeDefaultDescription
plugins.enabledboolfalseEnable plugin loading and execution
plugins.directorystring"./plugins"Directory containing installed plugins
plugins.registry_urlstring(built-in)Plugin registry URL for accent plugin install
plugins.fuel_limitintegerWASM instruction budget per plugin invocation (omit for unlimited)
plugins.compilation_cachestringDirectory for Wasmtime compilation cache (speeds up repeated loads)
plugins.precompileboolfalseAOT pre-compile plugins at load time for faster execution
plugins.allowed_pathsmap{}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.

FieldTypeDefaultDescription
versioning.rootsmap{}Versioning roots keyed by URL prefix (e.g., /docs). Empty means versioning is disabled.
versioning.roots.<prefix>.versionsordered map(required)Version definitions. Iteration order determines dropdown display order.
versioning.roots.<prefix>.versions.<id>.labelstring(required)Human-readable label (e.g., "1.1 (Latest)")
versioning.roots.<prefix>.versions.<id>.badgestringOptional badge hint for templates (e.g., "latest", "lts")
versioning.roots.<prefix>.versions.<id>.fallbackstringVersion ID to fall back to when a page is missing in this version
versioning.roots.<prefix>.defaultstring(required)Default version served when no version prefix is in the URL
versioning.roots.<prefix>.redirectsmap{}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):

  1. Built-in defaults – Hardcoded in Accent CMS
  2. config.yaml – Or the file specified by --config
  3. 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