config.json Reference
content/config.json is entirely optional and hot-reloads with the rest of content/. Every field below is optional too, with a sensible default filling in whatever's missing.
A config using most of them at once:
content/config.json
json{
"title": "Acme Status",
"titleTemplate": ":title · Acme Status",
"description": "Uptime and incident history for Acme's public services.",
"lang": "en",
"culture": "en-GB",
"theme": "midnight",
"author": "Acme Ops",
"favicon": "🛡️",
"scrollIndicator": true,
"noIndex": { "pages": true },
"socialLinks": [
{ "icon": "github", "url": "https://github.com/acme", "title": "GitHub" }
],
"menu": [
{ "title": "Status", "path": "/" },
{ "title": "About", "path": "/about/" }
],
"footerMenu": [
{ "title": "Status API", "path": "/api/status", "external": true }
],
"footer": "© {year} {author}. Built with [Warden](https://github.com/hawkinslabdev/warden).",
"monitoring": {
"intervalSeconds": 60,
"retentionDays": 90,
"group": "type",
"targets": [
{ "id": "site", "name": "Main site", "url": "https://acme.example" }
]
}
}Site
| Field | Description |
|---|---|
title |
The site name: browser tab, social previews, and the {title} token elsewhere. |
titleTemplate |
Wraps title for the tab text, e.g. ":title · Acme". :title is the placeholder. |
description |
The default meta description, used on any page that doesn't set its own. |
brand |
Text shown next to the logo in the topbar. Falls back to title. |
brandImage |
A logo image, replacing brand in the topbar. |
image |
Default social preview image for pages that don't set their own cover. |
favicon |
An emoji or an image path for the browser tab icon. |
author |
The name in the {author} footer token. organization, organisation, and owner are aliases, checked in that order, ahead of author. |
Language and dates
| Field | Description |
|---|---|
lang |
The page's <html lang> attribute and default UI language. |
culture |
Date and number formatting, e.g. "en-GB". |
locale |
Either a bare code string ("nl") or an object: { "culture": "en-GB", "code": "en" }. Merges with culture above. |
Appearance
| Field | Description |
|---|---|
theme |
One of the built-in palettes. See the guide for the full list. Add " dark" or " light" to pin a mode and drop the toggle. |
scrollIndicator |
Set false to hide the reading-progress bar at the top of the page. |
showOverallUptime |
Set false to hide the status page's overall-uptime line. /api/status never included it, so nothing changes there. |
Navigation
| Field | Description |
|---|---|
menu |
Topbar links: [{ "title": "...", "path": "..." }]. An entry with items renders as a dropdown instead of a link. external: true opens it in a new tab. |
footerMenu |
Same format, rendered in the footer. |
footer |
Markdown for the footer note. Tokens: {year}, {author}, {title}. Falls back to a simple copyright line built from title. |
socialLinks |
Icon links in the topbar: [{ "icon": "github", "url": "...", "title": "..." }]. |
head |
Raw tags injected into <head>: [{ "tag": "meta", "attrs": { "name": "...", "content": "..." }, "content": "..." }]. |
Search visibility
| Field | Description |
|---|---|
noIndex.pages |
Set true to noindex every standalone page under content/pages/ at once, without touching each page's own front matter. A page's own noindex: true still applies regardless. |
noIndex.status |
Set true to noindex the status page itself ("/") and drop it from sitemap.xml. |
redirectHosts |
Off-site hosts a page's redirect front matter is allowed to send readers to. Same-host redirects always work without listing anything here. |
Monitoring
monitoring controls what gets checked and how the status page groups it. See the guide for a walkthrough; this is the full field list.
| Field | Description |
|---|---|
intervalSeconds |
How often every target is checked. Default 60. |
retentionDays |
How long heartbeat history is kept before it's pruned. Default 30. This value cannot be lower than historyDays to preserve your data. |
group |
"type" sections monitors by type, "custom" sections them by each target's own group field (falling back to its type when unset). Unset renders one ungrouped list. |
incidentWindowDays, incidentMaxShown |
How far back and how many resolved incidents show on the status page. Defaults 7 and 10. |
historyDays |
Days of ticks in each monitor's history bar. Default 90. |
degradedBelowPercent |
A day's tick turns down (not just degraded) when that day's uptime% falls below this. Unset keeps the stricter default: down only when every check that day failed. |
incidentUrlPattern |
"year" gives every incident a /incidents/{year}/{slug}/ URL, "year-month" gives /incidents/{year}/{month}/{slug}/, both taken from the incident's own date, regardless of which folder the file lives in. Unset (default) uses the file's own path under content/incidents/ as-is. |
maintenanceWindowDays, maintenanceMaxShown |
How far ahead and how many upcoming maintenance windows show. Defaults 14 and 10. |
webhooks |
[{ "url": "...", "headers": { "Authorization": "Bearer ..." } }]; sent as POST with JSON body (monitorId, name, status, message, timestamp) once per down/recovery transition, not on every check while a target stays down. headers is optional, for receivers that need auth. |
targets |
The list of monitors, covered below. |
Target fields
Every target needs id (a stable slug, it's the database key) and name (the display label). Everything else depends on type.
| Field | Applies to | Description |
|---|---|---|
type |
all | http (default), service_backend, ping, tcp, database, ftp, sftp, dns, or ssl. |
url |
http, service_backend |
The address to request. |
host, port |
everything except http/service_backend |
Where to connect. Ports default per type: 21 for ftp, 22 for sftp, 443 for ssl. |
expectedStatus |
service_backend |
HTTP status the response must match. Default 200. |
expectedJsonPath, expectedValue |
service_backend |
A JSON path into the response body (e.g. $.status) and the value it must equal. |
expectedIp |
dns |
The IP the hostname must resolve to. Left unset, any successful resolution counts as up. |
dnsServer |
dns |
Query this resolver directly instead of the OS default. |
family |
dns |
"ipv4" or "ipv6" to query one record type only. Unset queries both. |
warnDaysBefore |
ssl |
How many days before certificate expiry the check starts failing. Default 14. |
secure |
ftp |
Set true to require an explicit AUTH TLS upgrade after connecting. |
dbType |
database |
Informational only; the check itself is a TCP reachability probe. |
insecure |
http, service_backend |
Set true to skip TLS certificate validation, for a self-signed internal service. |
retries |
all | Consecutive failures allowed before the target is recorded down. Default 0, down on the first failed check. |
group |
all | This target's own group heading, used when monitoring.group is "custom". |
hidden |
all | Set true to keep checking and recording this target while excluding it from the status page and /api/status - for a backend you track but don't publish. |
A few examples across types:
content/config.json
json{
"monitoring": {
"targets": [
{ "id": "site", "name": "Main site", "url": "https://acme.example" },
{ "id": "api", "name": "API health", "type": "service_backend", "url": "https://api.acme.example/healthz", "expectedJsonPath": "$.status", "expectedValue": "ok" },
{ "id": "ci", "name": "CI", "type": "service_backend", "url": "https://api.github.com/repos/OWNER/REPO/actions/workflows/WORKFLOW_FILE/runs?per_page=1&status=completed", "expectedJsonPath": "$.workflow_runs[0].conclusion", "expectedValue": "success" },
{ "id": "db", "name": "Postgres", "type": "tcp", "host": "db.internal", "port": 5432, "retries": 2 },
{ "id": "cert", "name": "Certificate", "type": "ssl", "host": "acme.example", "warnDaysBefore": 21 },
{ "id": "resolver", "name": "DNS", "type": "dns", "host": "acme.example", "expectedIp": "203.0.113.10" }
]
}
}