Settings and navigation
Configure your site identity, menus, active theme, and design tokens — all from the admin, no config files
Everything that configures your site — its name, the canonical URL, the header menus, the active theme, any design-token tweaks — lives in the settings table in D1, not in a config file and not in Wrangler. You edit it from the admin and it takes effect on the next render. Settings are versioned like every other entity, so site-wide time travel reconstructs exactly how your site was configured at any point in the past.
Typed settings
Each setting has a key (such as site.title) and a value that is validated against a Zod schema before it is written. The admin settings API rejects any key that is not declared in the registry with a 400 and the code UNKNOWN_SETTING_KEY — there is no silent passthrough. A value that fails its schema is rejected the same way, so a malformed locale or an over-length title never reaches the database.
The core site settings are:
- site.title — the site name, 1 to 200 characters. Falls back to the SITE_NAME deploy variable and then to the literal "Cusp" when unset.
- site.description — a short tagline, up to 500 characters, or empty to clear it.
- site.locale — an IETF-style tag such as en or en-GB. Defaults to en.
- site.homepage_page_slug — the slug of a published page to render at the root. Empty or unset means the home route shows your latest posts instead. The save handler also confirms the slug resolves to a published page before accepting it. Once a page is the homepage it lives at exactly one URL: a request for its own permalink (for example /welcome) 301-redirects to /, and the page appears once in the sitemap (as /) and never as an RSS item. See ADR 0060.
- site.public_base_url — the canonical public origin, covered in detail below.
The public base URL
site.public_base_url is the canonical origin Cusp uses to build absolute links: the sitemap, RSS feeds, OpenGraph tags, the admin's "view live" links, and the URLs in password-reset emails. It must be an http or https URL, or empty to clear it.
The setup wizard captures this value on first run and prefills it from the URL you are using to reach the wizard — which is the right answer for almost every deploy, whether you landed on a workers.dev subdomain or a custom domain. Because it is an ordinary setting and not a Wrangler variable, moving to a custom domain later is "edit the setting, save" rather than "redeploy". Saving it purges the surrogate keys for the sitemap, feeds, listing pages, and home page so absolute URLs are rebuilt.
Navigation menus
Navigation is stored under site.menus as a named map of menus. Each menu name is a lowercase identifier (such as primary or footer); each menu is a list of entries with a label and an href, and an entry may carry one level of children for a dropdown. The structure is intentionally one level deep — a child cannot have its own children.
- Up to 10 menus per site.
- Up to 30 entries per menu.
- Up to 30 children per entry, one level deep.
- Labels are 1 to 80 characters.
An entry's href must be an http or https URL, an absolute path (/about), an in-page fragment (#contact), or a slug shape (letters, digits, dashes, underscores, slashes). Hostile schemes such as javascript: or data: are rejected at the boundary — attribute escaping alone does not stop them, because the browser URL-decodes attribute values before parsing the scheme. The header reads the menu named primary; if you have not defined one, it falls back to the flat site.primary_nav list.
{
"primary": [
{ "label": "Home", "href": "/" },
{ "label": "Recipes", "href": "/recipes" },
{
"label": "About",
"href": "/about",
"children": [
{ "label": "Our story", "href": "/about/story" },
{ "label": "Contact", "href": "/about/contact" }
]
}
],
"footer": [
{ "label": "Privacy", "href": "/privacy" }
]
}Active theme
The active theme is a single setting, site.active_theme, holding both the theme id and the version in one JSON value. Writing it as one key means activation is atomic — there is no window where the id has changed but the version has not. The activation route confirms the theme is installed and ready before flipping the pointer. The renderer loads that theme's package from R2 and serves its components and stylesheet. Cusp also ships a built-in theme, Aurora, that needs no install — it renders whenever no other theme is active. It appears as a permanent row on the Themes screen, and "Use default" switches back to it at any time, so you can always return to a known-good look. Installing and activating the four packaged first-party themes — or one of your own — happens on the same Themes screen; the themes and plugins guide walks through that flow.
Design-token overlays
Themes ship a tokens.json declaring their design tokens — colours, type scale, spacing, radii, layout. You can override any token from the admin's Customise screen (/admin/customise) without rebuilding the theme or touching its stylesheet. Overrides are stored as ordinary versioned settings under two key families:
- theme.tokens.core.{path} — applied regardless of which theme is active. Use these for cross-theme choices like brand colour or type scale; they survive a theme switch.
- theme.tokens.{themeId}.{path} — scoped to one theme, applied only while that theme is active, and preserved across switches.
At render time the resolver picks, for each token path, the first value it finds: the theme-specific override, then the core override, then the theme's own default. It emits a small inline style block of CSS custom properties (such as --cusp-color-accent) in the page head, ahead of the theme's stylesheet, which consumes those properties via var(). The stylesheet itself is served from R2 with a long immutable cache and is never regenerated — only the tiny per-render style block changes. Saving an override purges the theme surrogate key so the next render reflects it.
Token paths use lowercase alphanumeric segments joined by dots and no hyphens, so the path-to-property transform is one-to-one: color.accent becomes --cusp-color-accent, text.h1.size becomes --cusp-text-h1-size. The customise UI surfaces the well-known tokens; the typed-settings layer accepts any structurally valid path, so a power user can override a token the UI does not yet expose through the headless API.
Because every setting on this page is versioned, you can browse and restore any earlier configuration the same way you do for content.
Next: Reference section