Get started →

Configuration

All configuration lives in ciderpress.config.ts at your repo root. Use defineConfig for type safety and autocompletion.

import { defineConfig } from 'ciderpress'

export default defineConfig({
  title: 'My Docs',
  description: 'Project documentation',
  pages: [{ title: 'Introduction', path: '/intro', include: 'docs/intro/*.md' }],
})

Configuration is loaded via c12. Supported file formats: .ts, .mts, .js, .mjs, .json, .jsonc, .yml, .yaml.

pages is the only required field. Every other top-level key is optional — minimal config produces a clean site with zero framework branding.

Site identity

Top-level scalar fields that identify the site itself.

FieldTypeRequiredDescription
titlestringyesSite title shown in browser tab, topbar, and (when copyright is auto) the footer notice
descriptionstringnoMeta description and home page hero headline
basestringnoBase URL the site is deployed under (e.g. '/', '/docs/')
versionstringnoVersion label rendered next to the brand in the topbar (e.g. 'v1.0'). Omit to hide
defineConfig({
  title: 'Acme',
  description: 'Documentation for the Acme platform',
  base: '/',
  version: 'v1.0',
  pages: [/* ... */],
})

brand

Brand chrome — icon, wordmark, hero background, favicon, and the inline FOUC loader. Defaults to invisible: omit any field to render nothing in that slot.

brand: {
  icon:    IconConfig,
  logo:    string | LogoFn,
  banner:  string | BannerFn,
  favicon: ImageSource,
  loader:  'apple' | 'classic' | false | LoaderConfig,
}
FieldTypeDescription
iconIconConfigSmall chip rendered before the wordmark in the topbar. See IconConfig
logostring | LogoFnWordmark in the topbar — image path or ({ theme }) => LogoImage | ReactNode
bannerstring | BannerFnHero background — image path or function returning an ImageSource or React node
faviconImageSourceBrowser-tab icon. See ImageSource
loader'apple' | 'classic' | false | LoaderConfigInline FOUC loader. false disables it; pass LoaderConfig for a custom component

BannerFn

type BannerFn = (params: { theme: LogoContext }) => ImageSource | React.ReactNode

The function receives the active theme context and returns either an image source or a React node. Use the React-node variant for SVG-defined hero art that needs to respond to theme tokens.

brand: {
  banner: ({ theme }) => ({
    src: theme.variant === 'dark' ? '/banner-dark.svg' : '/banner-light.svg',
    alt: 'Acme banner',
  }),
}

LoaderConfig

type LoaderConfig =
  | {
      content: string
      label?: string
      minDisplayMs?: number
      maxDisplayMs?: number
    }
  | {
      component: ComponentType
      label?: string
      minDisplayMs?: number
      maxDisplayMs?: number
    }
VariantDescription
contentStatic SVG/img string rendered as the loader backdrop
componentCustom React component. Renders post-hydration; the pre-hydration fallback is the backdrop only

label is read by screen readers. minDisplayMs and maxDisplayMs clamp visible duration so the loader doesn't flash or hang.

theme

Theming uses a single array of theme entries. The first entry is the default unless one is explicitly marked. Both the named-theme picker and the light/dark variant toggle are independent — themeSwitcher and variantSwitcher.

theme: {
  themes:           ThemeEntry[],
  defaultVariant?:  'light' | 'dark' | 'system',
  themeSwitcher?:   boolean,
  variantSwitcher?: boolean,
  overrides?:       Partial<ThemeColors>,
}
FieldTypeDefaultDescription
themesThemeEntry[]['mulled'] (when theme is omitted)Mix of built-in theme names and custom Theme objects. First entry is default unless one is marked
defaultVariant'light' | 'dark' | 'system'active theme's own defaultVariant ('dark' for every built-in)Initial light/dark variant. 'system' defers to the active theme's declared default
themeSwitcherbooleantrue when themes.length > 1Show the named-theme picker in the topbar
variantSwitcherbooleantrueShow the light/dark toggle in the topbar (auto-hidden when the active theme has only one variant)
overridesPartial<ThemeColors>Override individual color tokens across every theme in themes

ThemeEntry

type ThemeEntry =
  | BuiltInThemeName
  | ThemeInput
  | { name: BuiltInThemeName; default?: boolean }
  | (ThemeInput & { default?: boolean })

Built-in names and full custom theme definitions share the same array. Either form accepts a default: true marker to override the "first entry wins" rule.

theme: {
  themes: [
    'honeycrisp',
    {
      name: 'acme',
      default: true,
      colors: {
        brand: '#ff5a1f',
        text:  '#1a1a1a',
      },
    },
  ],
  defaultVariant:  'dark',
  themeSwitcher:   true,
  variantSwitcher: true,
}

pages

The information architecture tree. Required. Each entry is a Page — the same shape used for leaf documents, sidebar groups, and glob-discovered sections.

Renamed from sections (Page replaces the old Section interface). Children live on Page.pages (renamed from items).

Page

interface Page {
  // ---- Identity ----
  title: TitleConfig
  description?: string
  path?: string
  icon?: IconConfig

  // ---- Source (declare exactly one) ----
  include?: string | string[]
  content?: string | (() => string | Promise<string>)
  pages?: Page[]

  // ---- Navigation behavior ----
  nav?: {
    hidden?: boolean
    collapsible?: boolean
    island?: boolean
    root?: boolean
  }

  // ---- Landing page ----
  landing?: boolean

  // ---- Card behavior ----
  card?: CardConfig

  // ---- Default page metadata ----
  defaults?: Frontmatter

  // ---- Glob-discovery options ----
  discover?: {
    sort?: SortStrategy
    recursive?: boolean
    ignore?: string[]
    indexFile?: string
  }

  // ---- Per-page integration ----
  openapi?: OpenAPISpec
}

Identity

FieldTypeRequiredDescription
titleTitleConfigyesStatic string or derivation rule for auto-discovered children
descriptionstringnoOne-line description for this page's auto-generated landing card and OG meta
pathstringnoURL path this page mounts at (e.g. /guides). Omit for a sidebar-only grouping node
iconIconConfignoIcon rendered on the page's card and (when configured) in the sidebar

Source — declare exactly one

FieldTypeDescription
includestring | string[]File path or glob string(s); children auto-discovered
contentstring | (() => string | Promise<string>)Inline Markdown/MDX string, or async generator
pagesPage[]Explicit child nodes (renamed from items)

Grouped together so per-page chrome flags don't sprawl across the top level of Page.

FieldTypeDefaultDescription
nav.hiddenbooleanfalseHide this page (and children) from the sidebar entirely
nav.collapsiblebooleantrueRender as a collapsible group in the sidebar
nav.islandbooleanfalseRender as a sidebar island — children appear only when the user is inside this branch (renamed from standalone)
nav.rootbooleanfalseMark as a sidebar root — only one root active at a time; the topbar treats it as the active workspace

Landing + card

FieldTypeDefaultDescription
landingbooleantrue for pages with childrenRender an auto-generated landing page at this path listing children as cards
cardCardConfigHow this page appears as a card on its parent's landing
CardConfig
interface CardConfig {
  icon?: IconConfig
  scope?: string
  description?: string
  tags?: string[]
  badge?: { src: string; alt: string }
}
FieldTypeDescription
iconIconConfigCard icon. Defaults to a rotating color based on position in the parent's landing card grid
scopestringScope kicker rendered above the title (e.g. 'apps/', 'packages/')
descriptionstringOne-line description rendered under the card title (overrides the page's own description)
tagsstring[]Tag chips rendered below the description
badge{ src: string; alt: string }Logo badge rendered in the card's top-right corner

Card content resolves from this priority order (highest first): card.description → source file frontmatter descriptionPage.description.

defaults — default page metadata

Renamed from frontmatter. Same Frontmatter type — values here are merged into every child page's frontmatter; per-file YAML wins on conflict.

{
  title: 'API Reference',
  defaults: { aside: 'left', editLink: false },
  pages: [
    { title: 'Auth',  path: '/api/auth',  include: 'docs/api/auth.md' },
    { title: 'Users', path: '/api/users', include: 'docs/api/users.md' },
  ],
}

discover.* — glob-discovery options

Only applies when include is a glob. Renamed from the flat Section.{sort,recursive,exclude,entryFile} fields.

FieldTypeDefaultDescription
discover.sortSortStrategy'default'Sort strategy for discovered children. See SortStrategy
discover.recursivebooleantrueRecurse into subdirectories
discover.ignorestring[]Glob patterns ignored during discovery (renamed from exclude — gitignore vocab)
discover.indexFilestring'overview'Filename treated as the page's own content instead of generating a landing page (renamed from entryFile)

openapi

Per-page OpenAPI spec integration. Generates API operation pages under the page's path. See OpenAPISpec for the shape.

Examples

Leaf page from a single file:

{
  title:   'Architecture',
  path:    '/architecture',
  include: 'docs/architecture.md',
}

Group with explicit children:

{
  title: 'Guides',
  path:  '/guides',
  pages: [
    { title: 'Quick Start', path: '/guides/quick-start', include: 'docs/guides/quick-start.md' },
    { title: 'Deployment',  path: '/guides/deployment',  include: 'docs/guides/deployment.md' },
  ],
}

Glob-discovered section with discovery options:

{
  title:       'Reference',
  path:        '/reference',
  description: 'API and CLI reference.',
  include:     'docs/reference/**/*.md',
  discover: {
    sort:      'alpha',
    recursive: true,
    ignore:    ['**/draft-*.md'],
    indexFile: 'overview',
  },
}

apps, packages, workspaces

Top-level workspace surfaces. Kept flat — apps and packages are the common cases; workspaces is for arbitrary custom groups like "Integrations" or "Plugins".

apps:       Workspace[],
packages:   Workspace[],
workspaces: WorkspaceGroup[],

All three drive the home page card grid (via home.showcase), the auto-generated landing card on their parent, and the workspace introduction page.

Workspace

interface Workspace {
  title: TitleConfig
  description: string
  path: string
  icon?: IconConfig
  tags?: string[]
  badge?: { src: string; alt: string }
  include?: string | string[]
  pages?: Page[]
  defaults?: Frontmatter
  discover?: {
    sort?: SortStrategy
    recursive?: boolean
    ignore?: string[]
    indexFile?: string
  }
  openapi?: OpenAPISpec
}
FieldTypeRequiredDescription
titleTitleConfigyesDisplay name. Accepts the full TitleConfig (was plain string)
descriptionstringyesShort description for cards and the workspace landing page
pathstringyesURL prefix for this workspace's documentation
iconIconConfignoIcon for the home card and sidebar header
tagsstring[]noTech tags — case-insensitive, mapped to icons via the tech registry
badge{ src: string; alt: string }noLogo badge rendered in the card's top-right corner
includestring | string[]noSource file path(s) or glob pattern(s) for content discovery
pagesPage[]noExplicit child pages (mirrors Page.pages)
defaultsFrontmatternoDefault frontmatter injected into every discovered child page (mirrors Page.defaults)
discover(see Page)noGlob-discovery options. Same shape as Page.discover
openapiOpenAPISpecnoOpenAPI spec integration for this workspace

WorkspaceGroup

interface WorkspaceGroup {
  title: string
  description?: string
  icon: IconConfig
  items: Workspace[]
  link?: string
}
FieldTypeRequiredDescription
titlestringyesGroup display name
descriptionstringnoShort description
iconIconConfigyesGroup icon. Accepts the full IconConfig (was IconId only)
itemsWorkspace[]yesWorkspaces in the group (at least one)
linkstringnoURL prefix override (defaults to /${slugify(title)})
workspaces: [
  {
    title: 'Integrations',
    icon: { id: 'pixelarticons:integration', color: 'orange' },
    items: [{ title: 'Stripe', description: 'Payment processing', path: '/integrations/stripe' }],
  },
]

OpenAPISpec

Per-page or per-workspace OpenAPI integration. The same shape lives on Page.openapi and Workspace.openapi — declare it once at the mount point you want the API operation pages to live under.

interface OpenAPISpec {
  spec: string
  path: string
  title?: string
  sidebarLayout?: 'method-path' | 'title'
}
FieldTypeRequiredDescription
specstringyesPath to the OpenAPI document (.json, .yaml, or .yml), relative to the repo root
pathstringyesURL path the API operation pages mount under (must start with /)
titlestringnoSidebar group title (default 'API Reference')
sidebarLayout'method-path' | 'title'noHow operations appear in the sidebar — method-path shows GET /users; title shows the operation summary (default 'method-path')

When declared on a Workspace, path must be nested under the workspace's own path — that's checked at validate time. See the OpenAPI reference for a full walkthrough.

socials

Root-level array of social links. Single source of truth — both topbar.socials and footer.socials reference this list via true.

socials: SocialLink[]
interface SocialLink {
  icon: SocialLinkIcon | { svg: string }
  url: string
  label?: string
}
FieldTypeRequiredDescription
iconSocialLinkIcon | { svg: string }yesBuilt-in icon name or custom SVG
urlstringyesTarget URL
labelstringnoAccessible label (screen readers, hover title)

The Rspress mode/content discriminator is no longer exposed — every link is a URL link.

Built-in SocialLinkIcon values:

import type { SocialLinkIcon } from '@ciderpress/config'

// 'discord' | 'facebook' | 'github' | 'instagram' | 'linkedin' | 'slack'
// | 'x' | 'youtube' | 'gitlab' | 'X' | 'bluesky' | 'npm'

Any icon outside this set must be supplied as { svg: '<svg>...</svg>' }.

Boolean reference pattern

topbar.socials and footer.socials accept either true (reuse root socials) or SocialLink[] (override with a specific list for that surface).

socials: [
  { icon: 'github',  url: 'https://github.com/acme'  },
  { icon: 'discord', url: 'https://discord.gg/acme'  },
],
topbar: { socials: true },                                  // mirror root list
footer: { socials: [{ icon: 'github', url: '...' }] },      // footer-specific

topbar

Top navigation bar — nav items, primary CTA, social row, announcement banner.

topbar: {
  nav:           'auto' | NavItem[],
  cta?:          ButtonConfig,
  socials?:      true | SocialLink[],
  announcement?: AnnouncementConfig,
}
FieldTypeDescription
nav'auto' | NavItem[]Navigation items — see auto rule
ctaButtonConfigPrimary CTA button (also mirrored into the mobile nav)
socialstrue | SocialLink[]true reuses root socials; array overrides for the topbar only
announcementAnnouncementConfigAnnouncement banner rendered above the topbar

Auto-nav emits one top-level entry per root pages entry that has a path. Children are not flattened into dropdowns. Roots with nav.hidden: true are skipped. Workspaces declared via top-level apps / packages / workspaces are not included — they show on the home grid only. For dropdowns or workspace items in the topbar, use the explicit NavItem[] form.

interface NavItem {
  title: string
  link?: string
  items?: NavItem[]
  activeMatch?: string
}
FieldTypeRequiredDescription
titlestringyesDisplay text
linkstringleafTarget URL — required on leaf items, omitted when items is provided
itemsNavItem[]noDropdown children — when present, this entry renders as a menu
activeMatchstringnoRegex pattern matched against the current URL for active-state styling

AnnouncementConfig

interface AnnouncementConfig {
  id?: string
  lead?: string
  message: string
  cta?: { href: string; label: string }
  persistent?: boolean
}
FieldTypeDescription
idstringStable id — when present, dismissal persists in localStorage
leadstringHighlighted lead phrase rendered before the message (e.g. "NEW")
messagestringBody text
cta{ href: string, label: string }Optional CTA appended after the message
persistentbooleanWhen true, hides the dismiss button

Persistent sidebar chrome — links pinned above and below the nav tree, plus the optional promo card.

sidebar: {
  top?:    SidebarLink[],
  bottom?: SidebarLink[],
  promo?:  SidebarPromo,
}
FieldTypeDescription
topSidebarLink[]Links rendered above the sidebar nav tree (renamed from above)
bottomSidebarLink[]Links rendered below the sidebar nav tree (renamed from below)
promoSidebarPromoPromo card pinned to the bottom of the docs sidebar

Sidebar links use ButtonConfig directly — no separate type. The same text/href/variant/shape/icon vocabulary as every other button surface.

sidebar: {
  top: [
    { text: 'Home',   href: '/',                       icon: 'pixelarticons:home',   variant: 'ghost' },
  ],
  bottom: [
    { text: 'GitHub', href: 'https://github.com/acme', icon: 'pixelarticons:github', variant: 'secondary' },
  ],
}

SidebarPromo

interface SidebarPromo {
  title: string
  body: string
  cta: ButtonConfig
}
FieldTypeDescription
titlestringPromo headline
bodystringBody copy
ctaButtonConfigCTA button

badges

Badge configuration — glob rules that apply a badge (or a named status) by route, plus the group flag for collapsible-doc groups. Badges render on the sidebar, breadcrumb, and section cards. A page's own frontmatter or defaults badge/status wins over a rule. See the Badges reference for the full model.

badges: {
  rules: [
    { match: '/api/experimental/**', status: 'alpha' },
    { match: ['/v2/**', '/beta/**'], badge: { text: 'v2', variant: 'info' } },
  ],
  group: true,
}
FieldTypeDescription
rulesBadgeRule[]Glob rules applied by route path (see BadgeRule)
groupbooleanShow a collapsible-doc group's badge on every surface. Defaults to false (hidden everywhere to spare the chevron)

BadgeRule

interface BadgeRule {
  match: string | string[]
  badge?: string | BadgeConfig | array
  status?: string | string[]
}
FieldTypeDescription
matchstring | string[]Glob pattern(s) matched against the route path
badgestring | BadgeConfig | arrayAd-hoc badge(s) applied to matching pages
statusstring | string[]Named status id(s) applied to matching pages

Declare at least one of badge or status. match supports *, **, and ?.

statuses

The named status registry — the semantic layer over badges. A status is a reusable, documented preset referenced by id from a page's status field. Entries merge over the built-in defaults by id (matching ids override, new ids extend).

statuses: [
  {
    id: 'alpha',
    title: 'Alpha',
    description: 'Early and unstable — expect changes.',
    variant: 'warning',
  },
  {
    id: 'design-partner',
    title: 'Design Partner',
    description: 'Available to design partners only.',
    color: '#7c3aed',
  },
]
FieldTypeRequiredDescription
idstringyesReference handle used by status: <id>
titlestringyesChip label
descriptionstringyesHover tooltip
variantBadgeVariantnoTheme-aware color; ignored when color is set
colorstringnoRaw color — overrides variant

Unified footer config — the old top-level footer and site.footer are now one block.

footer: {
  message?:   string,
  copyright?: true | string | CopyrightConfig,
  columns?:   FooterColumn[],
  tagline?:   string,
  brandMark?: string,
  socials?:   true | SocialLink[],
}
FieldTypeDescription
messagestringFooter message text
copyrighttrue | string | CopyrightConfigtrue auto-generates from title + current year; string is verbatim; object is structured
columnsFooterColumn[]Link columns rendered in the footer grid
taglinestringSmall tagline rendered on the right side of the bottom strip
brandMarkstringBrand mark character rendered in the footer's brand block (default 'Z')
socialstrue | SocialLink[]true reuses root socials; array overrides for the footer only

copyright: true produces Copyright © <currentYear> <title>. using the top-level title and the year at build time. Pass a string to override verbatim, or a CopyrightConfig for structured company / DBA / year-range output.

CopyrightConfig

interface CopyrightConfig {
  company?: string
  dba?: string
  year?: number | { from: number }
}
FieldTypeDescription
companystringLegal company name (e.g. 'Acme Inc.')
dbastring"Doing business as" name
yearnumber | { from: number }Single year, or a range from from to the current year
footer: {
  copyright: { company: 'Acme Inc.', dba: 'Acme', year: { from: 2021 } },
  // → "Copyright © 2021–2026 Acme Inc. (Acme)"
}

FooterColumn

interface FooterColumn {
  heading: string
  links: Array<{ text: string; href: string }>
}
FieldTypeDescription
headingstringColumn heading
links{ text: string, href: string }[]Column links

Security note — every href in footer.*, sidebar.*, and topbar.* is validated through a safe-URL helper that rejects javascript:, data:, vbscript:, and file: schemes. Relative paths, fragment anchors, http://, https://, mailto:, and tel: are allowed.

Per-page chrome — the "Edit on GitHub" and "Report an issue" links rendered under every doc page. Flattened to the top level to match the industry pattern (VitePress, Nextra). Set either to false to disable that action site-wide.

editLink?:   false | EditLinkConfig,
reportLink?: false | ReportLinkConfig,

EditLinkConfig

interface EditLinkConfig {
  repo?: string
  branch?: string
  directory?: string
  label?: string
  url?: (page: ResolvedPage) => string
  onResolve?: (page: ResolvedPage) => void
}
FieldTypeDescription
repostring"org/repo" shorthand or full URL — feeds the auto-URL builder
branchstringBranch to link against (default "main")
directorystringSubdirectory inside the repo containing the docs (default: repo root)
labelstringVisible label (default "Edit this page on GitHub")
url(page: ResolvedPage) => stringCustom URL builder — overrides the auto-URL
onResolve(page: ResolvedPage) => voidAnalytics / telemetry hook fired when the link is resolved
editLink: {
  repo:      'acme/docs',
  branch:    'main',
  directory: 'docs',
  onResolve: (page) => track('edit-link.resolved', { path: page.path }),
},

ReportLinkConfig

Identical shape to EditLinkConfig. repo may be either "org/repo" shorthand or a full issues URL; default label is "Report an issue".

reportLink: { repo: 'acme/docs' },
// or disable site-wide:
reportLink: false,

feedback

Controls the "Was this page helpful?" yes/no widget rendered at the bottom of every doc page. Off by default.

feedback?: boolean | { question?: string }
ValueEffect
omitted / falseWidget does not render
trueWidget renders with the default question
{ question: '…' }Widget renders with a custom question
// enable with the default question
feedback: true,
// enable with a custom question
feedback: { question: 'Did this help?' },

home

Home page layout — hero, proof strip, features grid, showcase grid, split section, final CTA, and the render-order layout list.

home: {
  hero:       HomeHeroConfig,
  proof?:     HomeProofConfig,
  features?:  HomeFeaturesConfig,
  showcase?:  HomeShowcaseConfig,
  split?:     false | HomeSplitConfig,
  cta?:       HomeCtaConfig,
  layout?:    HomeLayoutEntry[],
}
FieldTypeDescription
heroHomeHeroConfigHeadline, tagline, actions, and the optional demo visual
proofHomeProofConfig"Used by …" strip (renamed from trust)
featuresHomeFeaturesConfigFeature cards grid
showcaseHomeShowcaseConfigGeneralized card grid — defaults to apps + packages + workspaces, accepts arbitrary page paths
splitfalse | HomeSplitConfigSplit section. false disables
ctaHomeCtaConfigFinal CTA band
layoutHomeLayoutEntry[]Render order. Accepts section id strings, objects, or React components

HomeHeroConfig

interface HomeHeroConfig {
  label?: string
  tagline?: string
  actions?: ButtonConfig[]
  demo?: false | HomeHeroDemoConfig
}
FieldTypeDescription
labelstringSmall label above the title (renamed from eyebrow)
taglinestringMarketing line under the title
actionsButtonConfig[]CTA buttons (typically up to 2)
demofalse | HomeHeroDemoConfigVisual next to the hero copy. false hides it

HomeHeroDemoConfig

A discriminated union covering both demo forms:

type HomeHeroDemoConfig = HomeHeroDemoImage | HomeHeroDemoTerminal

interface HomeHeroDemoImage {
  src: string
  alt?: string
  width?: number | string
  height?: number | string
}

interface HomeHeroDemoTerminal {
  command: string
  lines: { kind: 'ok' | 'info' | 'cmt' | 'err'; text: string }[]
  windowTitle?: string
}

The image form paints an <img> into the demo container; the terminal form keeps the framework's terminal chrome and renders the supplied command + output lines.

HomeProofConfig

interface HomeProofConfig {
  lead?: string
  names?: string[]
}
FieldTypeDescription
leadstringLead phrase (e.g. "used by", "powering teams at")
namesstring[]List of names (renders nothing when empty)

Renamed from home.trust / HomeTrustConfig — plain English over design jargon.

HomeFeaturesConfig

interface HomeFeaturesConfig {
  items?: Feature[]
  columns?: 1 | 2 | 3 | 4
  truncate?: TruncateConfig
  heading?: HomeSectionHeading
}
FieldTypeDescription
itemsFeature[]Feature cards (replaces the old top-level features array). Optional — omit to customise grid layout / heading without supplying cards
columns1 | 2 | 3 | 4Grid column count
truncateTruncateConfigMax visible lines before clipping with ellipsis
headingHomeSectionHeadingOptional section heading (label + title) above the grid

Each Feature:

interface Feature {
  title: string
  description: string
  link?: string
  icon?: IconConfig
}

HomeShowcaseConfig

Generalized card grid — the second home block. Replaces home.workspaces. Default source is the combined apps + packages + workspaces list; you can also point it at an arbitrary list of page paths.

interface HomeShowcaseConfig {
  columns?: 1 | 2 | 3 | 4
  truncate?: TruncateConfig
  heading?: HomeSectionHeading
  source?: 'workspaces' | string[]
}
FieldTypeDescription
columns1 | 2 | 3 | 4Grid column count
truncateTruncateConfigLine clamps for the card title/description
headingHomeSectionHeadingOptional heading above the grid
source'workspaces' | string[]Omit / 'workspaces' → apps + packages + workspaces. Array of page paths → arbitrary card set
home: {
  showcase: {
    columns: 3,
    source:  ['/products/cli', '/products/api', '/products/web'],
  },
}

HomeSplitConfig

A two-column split section (code/visual on one side, copy on the other). Pass false at the parent (home.split: false) to omit the section entirely.

interface HomeSplitConfig {
  title: string
  label?: string
  body?: string
  bullets?: string[]
  cta?: ButtonConfig
  visual?: HomeSplitVisual
}
FieldTypeRequiredDescription
titlestringyesSection title
labelstringnoSmall label rendered above the title (renamed from eyebrow)
bodystringnoBody copy rendered under the title
bulletsstring[]noCheckmark list rendered under the body
ctaButtonConfignoCTA button rendered at the bottom of the copy column
visualHomeSplitVisualnoVisual rendered in the opposite column

HomeSplitVisual

interface HomeSplitVisual {
  code: string
  language?: string
}
FieldTypeRequiredDescription
codestringyesCode snippet rendered as a syntax-highlighted preview
languagestringnoLanguage identifier for syntax highlighting (default 'ts')

HomeCtaConfig

interface HomeCtaConfig {
  title?: string
  subtitle?: string
  actions?: ButtonConfig[]
}
FieldTypeDescription
titlestringCTA headline
subtitlestringSupporting text
actionsButtonConfig[]CTA buttons (typically up to 2)

HomeLayoutEntry

type HomeLayoutEntry =
  | HomeSectionId
  | { sectionId: HomeSectionId }
  | { component: ComponentType<{ paths: Paths }> }
  | { component: string }
FormDescription
HomeSectionId (string)Shorthand — render the built-in section
{ sectionId }Object form; reserved space for future per-entry options (hidden, props, …)
{ component: ComponentType }Inline JSX (TSX configs)
{ component: string }Path to a component file (TS / JSON configs)

Shared home types

These small shapes are reused across multiple home blocks (features, showcase):

interface TruncateConfig {
  title?: number
  description?: number
}

interface HomeSectionHeading {
  label?: string
  title?: string
  subtitle?: string
}

TruncateConfig values are maximum visible lines before CSS line-clamp clips with an ellipsis. HomeSectionHeading.label is the small uppercase kicker rendered above the title.

HomeSectionId

type HomeSectionId = 'hero' | 'proof' | 'features' | 'showcase' | 'split' | 'cta'

Renamed from the old 'hero' | 'trust' | 'features' | 'split' | 'workspaces' | 'cta'.

home: {
  layout: [
    'hero',
    'proof',
    { component: () => <CustomTimeline /> },
    'features',
    'cta',
  ],
}

discover

Top-level cross-cutting discovery options. Only field is ignore — global glob patterns excluded from every page's auto-discovery.

discover?: {
  ignore?: string[],
}
FieldTypeDescription
discover.ignorestring[]Glob patterns excluded from every page's discovery (gitignore vocab)
discover: {
  ignore: ['**/draft-*.md', '**/internal/**', '**/_*.md'],
}

Per-page discover.ignore is appended to this list — globals always apply.

templates

Directory or directories holding custom document templates used by ciderpress draft. Each is a .md/.mdx file with label/hint frontmatter; the filename is the template type. Paths are relative to the repo root.

templates?: string | string[]
FieldTypeDescription
templatesstring | string[]Directory (or directories) of custom .md/.mdx template files
templates: ['docs/.templates', 'shared/templates'],

A custom template whose filename matches a built-in (e.g. guide.md) overrides it. .mdx templates scaffold to .mdx files. Templates are validated by ciderpress templates check and as part of check/build. See Templates for the authoring format and the SDK.

devServer

Dev-server configuration — controls how ciderpress dev binds and how the dev URL is presented in the terminal and browser auto-open. All fields are optional.

devServer?: {
  url?:  string,
  port?: number,
  host?: string,
  open?: boolean,
}
FieldTypeDefaultDescription
urlstringhttp://${host}:${port}Externally-visible URL. Replaces the default http://${host}:${port} in the "ready: …" terminal message and the browser auto-open target. The dev server still binds locally — this is a display + auto-open hint
portnumber6174Preferred port. ciderpress falls forward through a 5-port range when the preferred port is occupied. CLI --port overrides
hoststring'127.0.0.1'Bind interface — explicit IPv4 loopback so reverse proxies (portless, nginx, Caddy) pointed at 127.0.0.1 can reach the dev server. Set '0.0.0.0' to expose on every network interface (LAN / Docker / VM). CLI --host overrides
openbooleanfalseAuto-open the resolved URL in the default browser when the dev server becomes ready

CLI precedence: --port / --host / --url > devServer.{port,host,url} > built-in defaults.

Example — behind portless.sh

devServer: {
  url: 'https://docs.acme.localhost',
  open: true,
}

The dev server still binds localhost:6174; portless reverse-proxies the HTTPS hostname to that port. See the portless guide for setup.

Example — exposing to LAN / Docker

devServer: {
  host: '0.0.0.0',
  port: 6174,
}

Shared primitives

Types reused across multiple top-level keys. Same shape, same meaning, everywhere.

IconConfig

type IconConfig = IconId | { id: IconId; color?: IconColor } | { src: string; alt?: string }

Uniform across every position — brand.icon, Page.icon, Workspace.icon, WorkspaceGroup.icon, Feature.icon, ButtonConfig.icon. Either a plain Iconify identifier ('pixelarticons:book-open'), an Iconify id with explicit color, or an arbitrary image source.

TitleConfig

type TitleConfig =
  | string
  | {
      from: 'auto' | 'filename' | 'heading' | 'frontmatter'
      transform?: (text: string, slug: string) => string
    }

Uniform across every title field that supports derivation — Page.title and Workspace.title. Plain string for static titles, or a derivation rule for auto-discovered children. The transform hook receives the derived title and the filename slug. (WorkspaceGroup.title is a plain string only.)

fromSource
'auto'Fallback chain: frontmatter → first # heading → filename
'filename'Filename converted to title case (add-route.md"Add Route")
'heading'First # heading in the file
'frontmatter'title field in YAML frontmatter

ButtonConfig

interface ButtonConfig {
  text: string
  href: string
  variant?: 'primary' | 'secondary' | 'ghost'
  shape?: 'square' | 'rounded' | 'circle'
  icon?: IconConfig
}

Unified button vocabulary. Replaces the three old button shapes (HeroAction.theme, SidebarLink.style, and the third unnamed variant). Used by home.hero.actions, home.cta.actions, topbar.cta, sidebar.top / sidebar.bottom / sidebar.promo.cta.

FieldTypeDescription
textstringButton label
hrefstringClick target
variant'primary' | 'secondary' | 'ghost'Visual variant (was 'brand' | 'alt' | 'ghost')
shape'square' | 'rounded' | 'circle'Button shape
iconIconConfigOptional leading icon

ImageSource

type ImageSource =
  | string
  | {
      src: string
      alt?: string
      type?: string
      width?: number | string
      height?: number | string
    }

Universal image source — string path or a fully described image object. Used by brand.favicon, brand.banner (string form), Workspace.badge, and anywhere else an image is rendered.

SortStrategy

type SortStrategy =
  'default' | 'alpha' | 'filename' | 'none' | ((a: ResolvedPage, b: ResolvedPage) => number)

Used by Page.discover.sort and Workspace.discover.sort.

ValueBehavior
'default'Sections first, then pinned intro files (introduction, intro, overview, index, readme), then alphabetical by title
'alpha'Sections first, then alphabetical by title
'filename'Sections first, then alphabetical by source filename
'none'Preserve glob-discovery order
comparator(a: ResolvedPage, b: ResolvedPage) => number — sort by your own rule. Each ResolvedPage has title, link, and frontmatter. Your comparator owns the full order; sections-first is not applied

'default' is the implicit fallback when discover.sort is omitted.

Frontmatter

Page.defaults (and Workspace.defaults) take a Frontmatter value. Same type as before — carries the page metadata fields Rspress understands (title, description, aside, editLink, pageType, …) plus any custom keys you want injected.

{
  title: 'API Reference',
  defaults: {
    aside:    'left',
    editLink: false,
    pageType: 'doc',
  },
}

Per-file YAML frontmatter wins on conflict with defaults. See Frontmatter Fields for the full field schema.

References

  • Frontmatter — per-page metadata schema
  • Icon Colors — color values accepted by IconConfig
  • Content — how pages map your existing markdown into the site tree
  • Workspaces — when to use apps, packages, or workspaces
  • Themes — built-in theme names and custom theme definitions

Resources

  • c12 — the config loader used under the hood
  • Iconify — icon identifier search