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.base_path | string | "" | URL prefix for sub-path deployments (e.g. a GitHub Pages project site served under /repo). Normalized to a leading slash with no trailing slash. |
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.main_site | map | unset | Optional link block back to a main/product site this deployment fronts. When unset, the default theme renders no outbound affordances. |
site.main_site.url | string | required | Main-site URL the header wordmark and footer link target. Must be non-empty – an empty value is rejected at config load. |
site.main_site.label | string | site.name | Wordmark text. Fallback applies only when unset; an explicit empty string renders an icon-only wordmark. |
site.main_site.badge_label | string | "Docs" | Text of the header chip that links back to this site’s own home page. |
site.main_site.cta_label | string | unset | Call-to-action button text. The button renders only when both cta_label and cta_url are set. |
site.main_site.cta_url | string | unset | Call-to-action target URL (e.g. a pricing page). |
site: name: My Blog description: A personal blog about Rust and web development url: https://example.com language: en languages: [en, de, fr]
site.base_path
When set, every internal URL Accent emits – page links, theme assets, media, search fetch paths and result URLs, llms.txt links, .md redirect stubs, pagination, and tag links – carries the prefix. This lets a static build work correctly when served from a sub-path, such as a GitHub Pages project site (https://user.github.io/repo/).
base_path is normalized at load time: a leading slash is added if missing and a trailing slash is stripped, so repo, /repo, and /repo/ all resolve to /repo.
Auto-derivation: if site.base_path is left unset and site.url (or the --base-url build flag) contains a non-root path component, the base path is derived from it automatically and logged at INFO. For example, site.url: https://username.github.io/repo alone derives a base path of /repo, with no further configuration needed. An explicit site.base_path always wins over derivation, and setting it to an empty string explicitly disables derivation.
site: url: https://username.github.io/repo base_path: /repo # optional -- would otherwise be derived from the path in site.url
Divergence from site.url: an explicit base_path can be set to a value other than the path component of site.url – for example site.url: https://username.github.io/repo with base_path: /other. This is allowed, but absolute URLs (sitemap, feeds, canonical links, JSON-LD) always use site.url as their base and append the resolved base_path to it, so the result is https://username.github.io/repo/other/..., not https://username.github.io/other/.... Accent logs a startup warning when it detects this divergence. Set site.base_path to match the path already in site.url (or omit it and rely on derivation) unless the deployed site genuinely serves content at site.url’s path plus the extra prefix.
site.main_site
Declares the main/product site a satellite deployment (typically a docs
site) belongs to. Templates receive it as site.main_site, including a
derived host field for compact link text. The default theme uses the
block for its header brand split, call-to-action button, and footer
link; see Linking Back to a Main Site
in the Theming Guide for what renders where.
site: main_site: url: https://example.com label: Example # optional; defaults to site.name badge_label: Docs # optional; text of the back-to-home chip cta_label: Get a license # optional; CTA renders only with both cta_* keys cta_url: https://example.com/pricing
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.attachments
Deliberate file downloads for extensions outside the serving allowlist.
| Field | Type | Default | Description |
|---|---|---|---|
media.attachments.extensions | list | [] | Extensions (without the leading dot, matched case-insensitively) served as forced downloads |
By default, Accent only serves files whose extension is on a hardcoded
allowlist (images, video, audio, PDF, HTML, and common structured-data
formats); anything else – an install script, an archive, a certificate
– is silently excluded from both accent serve and accent build
output. Listing an extension here makes such files resolvable on every
media path (the shared /media/ library, page-local /content-media/
files, and the bare-URL alias), but they are always delivered as a
download: Content-Type: application/octet-stream with
Content-Disposition: attachment; filename="...". There is no way to
configure inline rendering for a new extension.
Rules:
- Attachment wins on overlap. Listing an extension that is also on
the inline allowlist (for example
pdforhtml) makes those files download instead of rendering. The list can only ever make delivery stricter, never looser. - Hard rejections stay hard. Markdown sources and dotfiles are
never served regardless of this list; listing
mdis a configuration error. - Static builds publish the files but cannot send the headers. A
statically hosted download may render as plain text in the browser
unless your host lets you configure a
Content-Dispositionheader for it. See offering file downloads. - The list is read per request, so a SIGHUP config reload applies changes without a restart.
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 attachments: extensions: ["sh", "zip", "pem"]
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.
accent build emits the same two routes, so a bare page-local URL
authored while developing against accent serve is deploy-safe: the
static output carries both output/content-media/<page>/<file> and
output/<page>/<file> for every page-local media file, and the two are
byte-identical. media.serve_at_page_url: false is a single opt-out that
covers both surfaces consistently – it disables the bare-URL alias in
accent serve and stops the build from emitting the bare-URL copy, while
the canonical /content-media/... route is unaffected in either mode. A
rendered page’s own output always wins: if a page-local media filename
would collide with a page’s index.html, the build keeps the rendered
page and skips the media copy at that path (logged as a warning), exactly
like a real page shadows the alias in accent serve.
One layout is a partial exception: pages that live in a numbered sort
directory (e.g. content/01.getting-started/default.md, URL
/getting-started) are never reachable at their bare URL in
either mode, because the bare-URL resolver is a literal filesystem path
join with no numeric-prefix stripping – only the canonical
/content-media/getting-started/... route (which is keyed by the clean
URL, not a filesystem path) works for these pages. This is a pre-existing
property of the serve_at_page_url alias, not something the build
introduces.
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
Hot reload performance
After a content change, the server rebuilds its content index in the background right away, so the browser’s auto-refresh lands on a warm cache instead of waiting for the rebuild itself. Concurrent requests share a single rebuild rather than each rescanning the content directory, and the search index refresh reuses the same shared index.
The rebuild cost scales with site size and build profile. On large
sites (hundreds of pages), run the dev server from a release build –
cargo run --release -- serve ... or a release accent binary –
which rebuilds the index an order of magnitude faster than a debug
build while keeping hot reload fully active.
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.
These settings apply to accent serve only — a static build writes files, and files carry no HTTP headers. To carry this policy onto a static host, set build.header_artifacts, which generates the host’s own header configuration from this same section.
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 |
http_headers.security.routes | list | [] | Per-route header overrides (first match wins) |
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"
Per-route overrides (http_headers.security.routes)
Scope a different policy to specific paths without touching the site-wide defaults. Each entry matches the request path against a glob pattern; entries are evaluated top-to-bottom and the first match wins, exactly like the cache and CORS route overrides.
| Field | Type | Description |
|---|---|---|
path | string | Glob pattern for the request path (required) |
content_security_policy | string | CSP for matching responses; replaces the page or media default |
frame_options | string | X-Frame-Options for matching responses |
referrer_policy | string | Referrer-Policy for matching responses |
Fields left unset inherit the corresponding global value. A matched route’s CSP replaces whichever default (page or media) would otherwise apply. X-Content-Type-Options: nosniff and the SVG download disposition are site-wide invariants and cannot be overridden per route.
Patterns support three forms: a trailing /* matches the prefix and everything below it (/content-media/designsystem/*), a bare path matches exactly, and general globs use * for one path segment and ** across segments (/content-media/**/*.html).
http_headers: security: routes: - path: "/content-media/designsystem/*" content_security_policy: "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'" - path: "/embed/*" frame_options: "DENY"
Precedence per header, highest first: a value set by the route handler itself, then the first matching route override, then the page/media default. Overrides are read per request, so a SIGHUP config reload applies them without a restart.
The typical use is un-breaking iframe-embedded HTML served through /content-media/, which the strict media CSP otherwise strips of styles, images, and scripts – see the iframe shortcode guide for the copy-paste example.
build
Static site generation settings, used by accent build.
| Field | Type | Default | Description |
|---|---|---|---|
build.fingerprint | object | disabled | Asset fingerprinting for cache busting – see the asset fingerprinting guide |
build.header_artifacts | array | [] | Deploy-target response-header artifacts to emit at the output root |
header_artifacts accepts "netlify" (writes _headers) and "apache" (writes .htaccess). Each artifact is generated from http_headers and media.attachments, so the served policy and the deployed policy cannot drift. A rule that a target’s syntax cannot express exactly fails the build rather than shipping a weaker artifact.
build: header_artifacts: ["netlify", "apache"]
See response headers on static hosting for what gets translated, the fail-closed contract, and the hosts that have no header mechanism at all.
build.routing
Routing directives written into those same artifacts. The emitter already derives the clean-URL rules on its own, because those express the URL shape the build committed to in its sitemap and canonical tags. This block is the opposite: facts about the deployment that the output tree cannot show.
| Field | Type | Default | Description |
|---|---|---|---|
build.routing.error_document | string | unset | Site-absolute path served for a 404 |
build.routing.canonical_host | string | unset | The one host this deployment answers on |
build.routing.redirects | array | [] | Redirects the host should serve, in order |
build: header_artifacts: ["apache"] routing: error_document: /404.html canonical_host: example.com redirects: - from: /install.sh to: https://example.invalid/install.sh - from: /old-page to: /new-page status: 302 # optional; 301 when omitted
canonical_host emits a 301 from the host’s www/apex counterpart –
www.example.com when set to example.com, and example.com when set to
www.example.com. It deliberately does not redirect every other host, because
that also captures preview and staging deployments, which are legitimately
reachable under another name. The redirect target is always https.
A redirect to starting with / is site-relative and carries the deployment
prefix along with from; anything else is used verbatim, which is how an
off-site target is written. status accepts 301, 302, 307 and 308.
The block is inert when absent, so a site that never sets it produces
byte-identical output. It currently has no expression in the netlify target,
which carries redirects in a separate _redirects file – configuring both is
a build error rather than a deployment silently missing its rules.
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.fuel_limit | integer | WASM instruction budget per plugin invocation (omit for unlimited) | |
plugins.render_budget_ms | integer | 50 | Wall-clock budget per plugin call, in milliseconds. Meters guest execution: a plugin suspended in a host call (send-mail, a data-store write) is not charged for the wait, so a slow mail relay cannot exhaust it. Not the same key as diagrams.plugins.timeout_ms, which bounds one diagram render. |
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 render_budget_ms: 50 compilation_cache: .cache/plugins precompile: true allowed_paths: /content: "ro:./content" /media: "ro:./media" /data: ./site/data
hub Free
The ecosystem hub: where accent plugin install (and, in future, theme and
template installs) resolve artifacts from. One section serves all three kinds,
because they come from one registry — this replaces the former
plugins.registry_url, which no longer exists.
| Field | Type | Default | Description |
|---|---|---|---|
hub.registry_url | string | (built-in) | Registry base URL: the raw-content root of the registry’s default branch |
hub.registry_public_key | string | Ed25519 SPKI public key a self-hosted registry signs with. Required whenever registry_url is not the default | |
hub.licenses | map | {} | Vendor licence keys for commercial artifacts, keyed by <kind>:<name> |
hub: registry_url: https://raw.githubusercontent.com/AccentCMS/hub-registry/main registry_public_key: null licenses: "plugin:acme-forms": "eyJhbGciOiJFZERTQSJ9..."
Registry documents are always signature-verified; registry_public_key selects
which key, never whether to check. Pointing registry_url at another
registry without supplying its key is a configuration error.
The section parses in every build, including one compiled without the plugin runtime — themes and templates install without it. A binary that cannot act on the section says so at startup rather than rejecting your config.
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 # base_path: /repo # only needed for sub-path deployments; otherwise derived from site.url 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