Skip to main content

Configuration

Site settings

Every non-navigation setting in nookdocs.config.json — banner, SEO, redirects, search prompt, footer, and the general project knobs.

Most of what's on this page is also editable from the Configurations drawer in the editor (the gear icon in the editor toolbar). The drawer writes to nookdocs.config.json in your repo, so saving from the UI is exactly the same as editing the file by hand.

General

namestring

Project display name. Used as the page title fallback, in the navbar (when no logo is set), and in OpenGraph/SEO tags. Defaults to the value you picked at project creation.

contentRootstringdefault: "docs/"

Repo folder containing your MDX files. Must end with /. The sync pipeline ingests only files under this prefix — anything else in the repo is ignored.

icons.library'lucide' | 'tabler' | 'fontawesome'default: "lucide"

Which icon library resolves icon names when your MDX or nav config uses icon="rocket". Unknown names silently fall back to the text label.

Banner

A dismissible promo bar that sits above the page header. Good for release announcements, migration notices, or incident messaging.

{
  "banner": {
    "content": "🚀 v3 is out — [read the release notes](/changelog)",
    "variant": "primary",
    "dismissible": true
  }
}
banner.contentstringrequired

The banner text. Markdown links work: [text](/path).

banner.variant'primary' | 'info' | 'warning'default: "primary"

Colour treatment. primary uses your brand colour; info uses blue; warning uses amber.

banner.dismissiblebooleandefault: true

When true, shows a close button. Dismissed state is held for the rest of the session (not persisted).

Appearance

appearance.default'system' | 'light' | 'dark'default: "system"

The colour scheme shown when a visitor has no preference stored.

appearance.strictbooleandefault: false

Hides the theme toggle entirely. Use when your brand demands a single, forced mode.

SEO

{
  "seo": {
    "title": "Acme Documentation",
    "description": "Build, deploy, and scale with Acme.",
    "ogImage": "/og-default.png",
    "indexing": "default",
    "metatags": {
      "twitter:site": "@acme",
      "theme-color": "#0ea5e9"
    }
  }
}
seo.titlestring

Default page title. Page frontmatter title wins when present. Final format: {page title} | {seo.title}.

seo.descriptionstring

Default meta description. Page frontmatter description wins when present.

seo.ogImagestring

URL or repo-relative path of the Open Graph image. 1200×630 PNG recommended for Twitter/LinkedIn/Slack. Applied to og:image and twitter:image.

seo.indexing'default' | 'noindex'default: "default"

When noindex, a robots: noindex, nofollow meta tag is emitted on every page. Use for staging sites or private docs.

seo.metatagsRecord<string, string>

Free-form meta-tag map. Keys become <meta name="KEY" content="VALUE">. Appended to every page. Common keys: twitter:site, theme-color, color-scheme, robots.

seo.faviconstring

Path to a favicon (SVG preferred). Applied as <link rel="icon"> globally.

Search

search.promptstringdefault: "Search docs…"

Placeholder text in the search bar. Plain text only.

Redirects

URL redirects applied server-side. Useful when you rename pages, restructure tabs, or migrate from another platform.

{
  "redirects": [
    { "source": "/old/guides", "destination": "/guides", "permanent": true },
    { "source": "/api/v1/*", "destination": "/api/v2/*" }
  ]
}
redirects[].sourcestringrequired

URL path to match. Trailing /* enables prefix matching; the matched tail is preserved when the destination also ends in /*.

redirects[].destinationstringrequired

Where to send the visitor.

redirects[].permanentbooleandefault: false

When true, returns HTTP 308 (permanent). Otherwise 307 (temporary). Search engines respect 308 and transfer ranking signals to the new URL.

Navbar

navbar.links{ label, href }[]

Text links rendered in the top bar, left of the primary CTA. Each needs a label + href.

A root-relative href (/pricing) is resolved against your site's base path, so it keeps working when the docs are served under a subpath like acme.com/docs. Write the path as it appears in your navigation and leave the prefix to us. Absolute URLs are used as given.

navbar.controls{ appearance?, language?, version?, product?, order? }

Where the navbar's controls sit, and in what order. Each accepts "header", "sidebar", or "auto" (the default — follow the theme).

Every theme already has an opinion about each control — some put the appearance toggle at the bottom of the sidebar and keep the language switcher in the header, others group them. Setting a control here overrides the theme for just that control, so you can move one without disturbing the rest.

order re-sequences whichever controls end up in the header, left to right. Anything you leave out keeps its usual position after the ones you list — so ["appearance"] pulls the toggle to the front and leaves the rest alone.

The sidebar slot exists on themes whose footer renders controls (Birch, Slate, Larch). On a theme that keeps its footer empty there is nowhere to put them, so a control sent to the sidebar simply doesn't render — check your theme before relying on it.

On the headerless themes (slate, larch) the rail is the default home for all of them, since there is no header row to stack them in — the assistant excepted, which stays beside the CTA where a reader looking for an action finds both. Setting a control to "header" still moves it there.

{
  "navbar": {
    "controls": {
      "language": "header",
      "appearance": "sidebar",
      "order": ["language", "version"]
    }
  }
}

These are in the dashboard under Configurations → Layout → Navbar, where the order is set by clicking controls in the sequence you want.

navbar.primaryCta{ label, href, style?, color?, textColor?, external? }

Call-to-action button pinned to the right of the navbar, just before the light/dark toggle. Use it for the most common next step (Dashboard, Sign in, Start free). Omit the block entirely to hide the button.

navbar.primaryCta.style"solid" | "outline" | "ghost"default: solid

Shape of the button. solid is a filled pill, outline draws a border with no fill, and ghost is text only — useful when the CTA shouldn't outrank the docs themselves.

navbar.primaryCta.colorstring

Fill (or border) colour — any CSS colour. Leave it out and the button follows your theme's accent, which is the right choice for most sites. Set it when the CTA belongs to a product whose brand colour differs from the docs theme.

navbar.primaryCta.textColorstring

Label colour on a solid button. Leave it out to follow the theme.

navbar.primaryCta.externalboolean

Open in a new tab. Defaults to true for http(s) targets and false for docs paths, so you rarely need to set it.

An external target opens with rel="noopener" — it cannot reach back into the docs tab, and it does arrive with a referrer. That second part is deliberate: your own analytics will show these visits as coming from your documentation rather than as Direct, which is the number that tells you whether the docs are doing their job. The same applies to external navbar tabs, sidebar anchors and dropdown items. Links written inside a page keep rel="noopener noreferrer", since those point anywhere on the web.

{
  "navbar": {
    "links": [
      { "label": "Changelog", "href": "/changelog" },
      { "label": "Pricing", "href": "https://acme.com/pricing" }
    ],
    "primaryCta": { "label": "Dashboard", "href": "https://app.acme.com" }
  }
}

A button that carries its own brand colour rather than the theme's:

{
  "navbar": {
    "primaryCta": {
      "label": "Dashboard",
      "href": "https://app.acme.com",
      "style": "outline",
      "color": "#6366f1"
    }
  }
}

You can edit all of these from the dashboard under Configurations → Layout → Navbar — the colour fields there leave the button on the theme accent while they're empty.

Navigation layout

Every theme picks where the navigation chrome sits — Oak gives tabs their own strip below the logo, Sage centres them on a single row, Larch drops the header bar entirely and lets the sidebar own the full height. layout departs from that choice without leaving the theme, so you keep its palette and typography while rearranging the frame.

Each field defaults to auto, meaning "whatever the theme says". Leave it there and a later theme update flows through; set it and your choice sticks across theme changes.

layout.shell'auto' | 'header-sidebar' | 'sidebar-first' | 'header-only'default: auto

Which shell frames the page. header-sidebar is a header bar above a sidebar, sidebar-first drops the header so the rail runs full height, and header-only has no rail at all. The largest of these overrides — it swaps the whole frame, not just where the tabs sit.

layout.tabPosition'auto' | 'header' | 'sidebar-list' | 'sidebar-dropdown'default: auto

Where navigation tabs render — across the header, or down the sidebar as a list or a dropdown.

layout.headerRows'auto' | '0' | '1' | '2'default: auto

How many rows the header takes. 0 removes the header bar, 1 keeps tabs on the logo row, 2 gives them their own strip beneath it.

layout.tabStyle'auto' | 'underline' | 'pill' | 'text' | 'text-right'default: auto

How header tabs are drawn.

layout.tabAlign'auto' | 'start' | 'center' | 'end'default: auto

Horizontal placement of single-row tabs. Ignored when headerRows is 2, where the strip spans the full width anyway.

Larch with Oak's two-row header, keeping Larch's warm palette:

{
  "theme": "larch",
  "layout": {
    "shell": "header-sidebar",
    "tabPosition": "header",
    "headerRows": "2"
  }
}

layout is a top-level key, not nested under navbar or navigation. It changes placement only — no navigation entry, page, or URL is affected, so the Markdown export is identical whatever these are set to.

In the dashboard: Configurations → Layout → Navigation layout. Every field has a Theme default option that writes nothing to your config.

Sidebar

Every theme makes its own calls about the sidebar — whether pages carry icons, how the current page is marked, whether the scrollbar is always there. These keys let you depart from your theme on any one of them without giving up the rest of it.

Leave a key out (or set it to "auto") and the theme decides. That is the point: a site that hasn't opted out keeps behaving sensibly when you switch themes later.

sidebar.activeStyle'auto' | 'bg-tint' | 'text-only' | 'text-tint' | 'caret'default: auto

How the current page is marked. bg-tint fills a pill behind it. text-only draws a thin rail down every row with an accent bar on the active one. text-tint colours the label and paints nothing else — the quietest, for content-heavy sidebars where a pill competes with the text. caret puts a chevron in the margin beside the active page and draws no rail at all; the marker sits outside the row, so labels stay on one line as you navigate.

sidebar.showIcons'auto' | 'true' | 'false'default: auto

Whether per-page icons appear. A string rather than a boolean so that "off" stays distinguishable from "not set" — "false" hides icons even on a theme that shows them.

sidebar.scrollbar'auto' | 'always' | 'hover'default: auto

Whether the sidebar's scrollbar is always drawn or fades in on hover. Touch devices never show one either way.

sidebar.groupLabelStyle'auto' | 'normal' | 'uppercase' | 'uppercase-mono'default: auto

Casing and weight of group headings. uppercase reads as formal section markers; normal sits closer to the pages beneath it.

sidebar.collapsibleGroups'auto' | 'true' | 'false'default: auto

Whether groups get a chevron toggle. "false" keeps every group open with no toggle at all — best for a short sidebar where hiding anything costs more than it saves. A string rather than a boolean so "off" stays distinguishable from "not set".

sidebar.defaultGroupState'auto' | 'expanded' | 'collapsed'default: auto

How collapsible groups start. expanded opens all of them; collapsed opens only the group holding the current page and leaves the rest shut — the default. Has no effect when groups aren't collapsible, since there is nothing to close.

A per-group expanded in your navigation still wins over this, so you can force one section open regardless.

{
  "sidebar": {
    "activeStyle": "text-tint",
    "showIcons": "false"
  }
}

These are in the dashboard under Configurations → Layout → Sidebar, each with a Theme default option that writes nothing to your config.

View switcher

Pages that use <View> get one switcher above the content. This chooses how it's drawn.

viewSwitcherStyle'auto' | 'dropdown' | 'tabs'default: auto
  • dropdown (the default) — one control stating the current variant. Stays one line however many variants a page offers.

  • tabs — the variants spread in a row. Reads well for two or three; wraps into several rows past that, and stacks against any tabs the page itself uses.

{
  "viewSwitcherStyle": "tabs"
}

In the dashboard: Configurations → Layout → Content → View switcher.

Code blocks

Every theme frames fenced code blocks its own way. codeBlockStyle overrides that choice for your whole site.

codeBlockStyle'auto' | 'card' | 'inset' | 'terminal'default: auto

How fenced code blocks are framed.

  • auto — follow the theme. Most themes draw card; Larch draws inset and Terminal draws terminal.

  • card — a bordered block with a filled header strip carrying the language and the copy button.

  • inset — no border. The block is a soft raised surface with the code recessed into a darker well inside it, so depth does the separating instead of an outline. Suits pages that read as one continuous canvas.

  • terminal — sharp corners and a $ prompt marker before the language, for docs whose examples are mostly commands.

{
  "codeBlockStyle": "inset"
}

codeBlockStyle is a top-level key, not nested under styling or sidebar. It is purely presentational — it changes no markup semantics, so fenced code blocks are extracted identically from the Markdown export regardless of its value.

In the dashboard: Configurations → Layout → Content → Code blocks. Leaving it on Theme default writes nothing to your config, so switching themes keeps each theme's own framing.

Footer

footer.style: columns on this site

The footer renders beneath the page content (not under the sidebar). In page style it stays a single compact row when it holds only social icons and a copyright line; add a logo or legal links and it grows into a tidy two-tier layout (brand on top, legal + copyright and the attribution on a divided row below) so nothing floats out of alignment.

{
  "footer": {
    "style": "page",
    "social": {
      "github": "https://github.com/acme",
      "x": "https://x.com/acme",
      "discord": "https://discord.gg/acme"
    },
    "links": [
      {
        "title": "Product",
        "items": [
          { "label": "Features", "href": "/features" },
          { "label": "Pricing", "href": "/pricing" }
        ]
      }
    ]
  }
}
footer.style'none' | 'page' | 'columns'default: "none"

none hides your footer content — logo, social, legal links, and copyright are all suppressed even if you've set them, so it's a real off switch (free-plan sites still keep the small "Powered by NookDocs" mark). page renders a compact footer beneath the page content — a single social-icon + copyright row when that's all it holds, expanding to a two-tier layout (brand on top, legal + copyright below) when you add a logo or legal links. columns renders a multi-column link grid with the social row and a legal row below.

footer.width'reading' | 'content' | 'full'default: "reading"

How wide the footer's surface runs — independent of footer.style, so it applies to page and columns footers alike. reading (the default) lines it up with the reading column — the same narrow width as your page text and the "Was this helpful?" feedback block. content widens it to the article + table-of-contents column. full spans the whole viewport — including under the sidebar, which stops at the footer's top rule — so the footer reads as a bar closing the page. A columns footer reflows its link columns to fit the chosen width. Leave it out to follow your theme, then the reading default.

footer.logobooleandefault: false

Show your site logo in the footer. Reuses colors.logo — no separate upload or path. In columns style it sits with the social icons on the left, beside the link columns; in page style it moves the footer into its two-tier layout, with the logo above the social row.

footer.socialRecord<string, string>

Platform slug → profile URL. Rendered as brand icons in the page footer (set style to page or columns to show them). Supported slugs: github, x, linkedin, discord, youtube, facebook, website, slack, instagram, hackernews, medium, telegram, bluesky, threads, reddit, podcast. (twitter is accepted as an alias for x.)

footer.links{ title: string; items: { label: string; href: string }[] }[]

Column definitions. Each column has a title and an array of links. Only rendered when style: "columns".

Social links live in the page footer only — they no longer appear under the sidebar. The "Powered by NookDocs" attribution also lives in the page footer, aligned to its end edge; the sidebar's bottom strip is theme-level chrome (appearance/language controls or a Dashboard link, depending on the theme).

Typography

Override the theme's default font stacks. Each field drops its value into a CSS custom property (--docs-font-sans / --docs-font-heading / --docs-font-body) read by every theme. Leave empty to inherit the active theme's fonts.

typography.fontFamilystring

Catch-all font family — used when a single face serves both display and body text. Sets --docs-font-sans.

typography.headingFontstring

Font stack applied to headings (--docs-font-heading). Falls through to fontFamily when omitted.

typography.bodyFontstring

Font stack for paragraph + inline content (--docs-font-body). Falls through to fontFamily when omitted.

{
  "typography": {
    "headingFont": "'Geist', system-ui, sans-serif",
    "bodyFont": "'Inter', system-ui, sans-serif"
  }
}

Advanced

advanced.notFound.activebooleandefault: false

Enable the custom 404 page. When off, falls back to the platform default.

advanced.notFound.titlestring

Headline shown on the 404 page.

advanced.notFound.descriptionstring

Subtitle copy. Supports markdown.

advanced.notFound.redirectstring

When set, the 404 page is skipped and the visitor is server-side redirected here instead. Common pattern: "/introduction" — missing URLs fall back to the first real page. Takes priority over title/description when both are set.

advanced.showTimestampbooleandefault: true

Show "Last updated on DATE" at the bottom of each page. Uses the most recent commit touching that file. Individual pages can pin a specific date via the frontmatter timestamp field — see Frontmatter fields.

AI Assistant

{
  "ai": {
    "assistant": {
      "enabled": true,
      "model": "qwen/qwen3.5-plus-02-15",
      "systemPrompt": "Answer concisely. Cite sources inline."
    }
  }
}
ai.assistant.enabledbooleandefault: false

Show the floating chat bubble on every docs page. Opt-in.

ai.assistant.modelstring

Model identifier — only applied when you bring a matching provider key (BYOK). Without a BYOK key the assistant uses the AI included in your plan and this field is ignored.

ai.assistant.systemPromptstring

Override the default system prompt. Use this to constrain tone, persona, or scope. The platform appends its own RAG context block below your prompt.

Metadata

Free-form key-value pairs that ship with the project config. Exposed to MDX as {config.metadata.KEY} (planned), handy for {version} / {releaseDate} substitutions in templates.

{
  "metadata": {
    "version": "2.0.0",
    "releasedAt": "2026-03-01"
  }
}

Interaction

interaction.drilldownbooleandefault: true

Controls group-label click behaviour. When true (default), clicking a group with root set navigates to that page. When false, clicking only toggles expand/collapse.

Contextual menu

The "Copy page" dropdown rendered in the top-end corner of every page header. Configurable from Configurations → Layout → Contextual menu, or via nookdocs.config.json#contextual.

contextual.enabledstring[]

Whitelist of built-in sections. Accepts "content", "ai-platforms", "mcp", "custom". Omit to show all (default).

contextual.optionsArray<{title, description?, icon?, href}>

Custom menu items appended to the Custom section. href supports $page / $path / $title / $mdx template variables resolved at click time.

contextual.display'header' | 'toc'default: header

Where to render the menu. "toc" placement is queued for a follow-up release and not yet surfaced in the dashboard.

See the full contextual menu reference for every built-in option and template-variable example.

How to edit these

  • Dashboard — Configurations drawer (gear icon in the editor). Writes back to nookdocs.config.json via git commit on every save.

  • Repo — edit nookdocs.config.json directly; the sync pipeline picks up the change on the next push.

Either path works. The dashboard never rewrites keys it doesn't understand, so you can hand-edit advanced fields and they'll survive dashboard saves untouched.

Was this page helpful?

Last updated August 30, 2026