Configuration

Every option in feastdocs.config.mjs.

One file configures the site: feastdocs.config.mjs at the project root. It is read by the content build and emitted as a typed module the app imports, so both halves of the framework always agree.

feastdocs.config.mjs
export default {
  title: 'FeastDocs',
  tagline: 'Documentation that lives next to the code.',
  logo: null,
  docsDir: 'docs',
  navbar: {
    links: [{ label: 'Docs', to: '/intro' }],
  },
  footer: {
    text: '© 2026 FeastDocs',
    links: [],
  },
  theme: {
    defaultMode: 'system',
    accent: '#f0812c',
    accentDark: '#ff9d52',
  },
  sidebar: {
    autoCollapse: false,
  },
  editUrl: null,
  showLastUpdated: true,
};

azureDevOps

The repository this documentation lives in, for editing the deployed site in the browser. Set entra as well — without a sign-in there is no token to call the API with.

Collection or organisation URL: `https://dev.azure.com/` for Azure DevOps Services, `https:///tfs/` for an on-premises server. Project name. Repository name. The branch pull requests target. It is never committed to directly — a publish creates a branch and opens a pull request, which is the only route a protected branch allows.

entra

Microsoft Entra ID sign-in. Both values are public — they ship in the JavaScript bundle either way, and a browser app has no client secret to protect.

The app registration needs to be a single-page application, with every origin the site is served from listed as a redirect URI (including http://localhost:4200 if you want to try it against a dev server).

Directory (tenant) id. Application (client) id of the registration. While it is null no sign-in is offered and none of the Entra code runs. Scope requested when publishing. The default is the well-known Azure DevOps resource; an on-premises server federated with Entra may expose its own application id instead, in which case use that.

Signing in asks only for identity. The scope above is requested when something is about to be published, so a reader who never edits is never asked to consent to repository access. The token belongs to the reader, so the commit carries their name and the site holds no shared credential.

Site

Option Type Effect
title string Navbar brand and the suffix of every browser title
siteUrl string | null Public origin (e.g. https://docs.example.com). Enables SEO output at build time: prerendered HTML per page, canonical/Open Graph tags, sitemap.xml, robots.txt
tagline string Fallback meta description for pages without one
logo string | null Path to an image inside public/, shown before the title
docsDir string Where the content lives, relative to the project root

Links take either to for an internal route or href for an external URL. External links open in a new tab and are marked as such.

footer.text and footer.links render in the site footer. When github.repo is set, a "Source on GitHub" link is added for free — in the footer and as a GitHub mark in the navbar — so readers can find the project and clone it.

navbar: {
  links: [
    { label: 'Docs', to: '/intro' },
    { label: 'API', to: '/reference/configuration' },
    { label: 'Repository', href: 'https://dev.azure.com/example' },
  ],
}

Theme

Option Type Effect
defaultMode 'system' | 'light' | 'dark' What a first-time reader gets
accent CSS colour Accent in light mode
accentDark CSS colour Accent in dark mode

Both accents are written to :root as custom properties at startup, so they are available to every stylesheet — including page-scoped SCSS.

A reader's own choice is stored in localStorage and wins over defaultMode from then on. A small inline script in index.html applies it before Angular boots, so there is no white flash on load.

System default and the first paint

The pre-boot script trusts a stored choice, and otherwise follows the operating system. If you set defaultMode: 'light' while the reader's OS is dark, the very first frame may be dark before the configured default takes over.

Editing and metadata

Option Type Effect
editUrl string | null Base URL of a repo file view; the page's source path is appended
showLastUpdated boolean Show the source file's modification date in the page footer
editUrl: 'https://github.com/acme/docs/edit/main/',

Where the date comes from

showLastUpdated reads the file's modification time on disk. In a fresh clone or a clean CI checkout that is the checkout time, not the last edit — if the real authoring date matters, drive it from your VCS instead.

GitHub

Option Type Effect
github.repo string | null owner/name; enables the content manager's GitHub mode (web edits become commits)
github.branch string Branch that web edits are committed to (default main)
github.oauthClientId string | null OAuth App client id; enables the "Sign in with GitHub" button
github.oauthScope string Scope requested at sign-in — public_repo for a public repo, repo if private (default repo)

See Use it for your own docs for a full setup walk-through, including CI.

Editor

Option Type Effect
editor.invite string | null Label on the navbar's content-manager link, shown until a reader opens it once. null (the default) keeps a plain icon

Teams running their own docs already know the editor is there, so the default is a quiet icon. A public demo is the case for an invitation — set invite: 'Try it now' (any wording, in any language) and first-time visitors get a labelled link that retires itself after they use it.

Option Type Effect
socialImage string | null Image in public/ used as og:image. null gives a text-only card

LinkedIn, Slack, X and WhatsApp all read Open Graph tags when someone shares a link. Without an image they render a bare text card, which is far less likely to be clicked. Point socialImage at a 1200×630 PNG or JPG in public/ — none of these platforms render SVG — and the build emits og:image, og:image:alt, twitter:image and upgrades the Twitter card to summary_large_image.

The URL is absolute, built from siteUrl, so link previews only work once siteUrl is set. Platforms cache aggressively: after changing the image, force a refresh with LinkedIn's Post Inspector.

Versions

Option Type Effect
versions array Documented versions. Omit for an unversioned site

Each entry is { id, label, docsDir, default, slug, editUrl }. See versioning.

API reference

Option Type Effect
openapi array OpenAPI documents to generate endpoint pages from

Each entry is { spec, outDir, label }. See API reference from OpenAPI.

Option Type Effect
sidebar.expand 'active' | 'all' | 'none' How a section's categories start when it does not say for itself. Default 'active'

'active' opens only the branch holding the current page, 'all' opens everything, 'none' opens nothing. A section overrides this with expand in its _section.json, and a category overrides that with its own expand — see controlling what starts open.

The older sidebar.autoCollapse still works: true maps to 'active', false to 'all'. Set expand and it wins.

Reusable content

Option Type Effect
variables object Values usable in any page as {{ name }}. Nested objects work: {{ a.b }}

Snippets need no configuration: a file in docs/_snippets/ is available as {{ snippet:its-name }} anywhere.

Both resolve at build time, so the shipped HTML and the search index contain the real text. Neither is applied inside code fences or backticks — braces are too common in code for that to be safe. An undefined variable is left visible and reported as a build warning rather than silently deleted.

See reuse and layout for worked examples.

Option Type Effect
sourceRepo string | null Repository the navbar and footer source links point at. Defaults to github.repo

Most sites need nothing here: the links follow github.repo. Set sourceRepo when the code people should clone is a different repository from the one the site is built and edited from — this site points at its starter template, while web editing and changelog commit links stay on the repository behind the site.

Changelog

Option Type Effect
changelog.limit number How many commits the build reads from git log. Default 150
changelog.repos array Other repositories to collect, as owner/name or {provider, org, project, repo}
changelog.monthlyPages boolean Generate a page per month under a category per year. Default false
changelog.monthlyPagesDir string Where those pages go, relative to docsDir. Default 'changelog'
changelog.branch string | null Branch to read history from. null (default) uses the checked-out branch
changelog.groupByRepo boolean | 'auto' Group generated pages under a category per repository. Default true
changelog.selfLabel string | null Category label for this repository. Defaults to the repo name

How the generated tree works, and what stays yours: how the changelog pages work.

The <fd-changelog> component renders repository history collected at build time. The limit bounds the generated data, so raising it costs a slightly larger lazy chunk and nothing else. Attribution needs real history, so CI must clone with full depth (fetch-depth: 0).

autoCollapse is reserved for collapsing every category except the active one. Collapse state is otherwise per-reader: expanding or collapsing a category is remembered in localStorage, and the branch containing the current page is always revealed.

Layout and spacing

Layout is not configured here — it lives in the design tokens, because that is where CSS belongs:

src/styles/custom.scss
:root {
  --fd-content-max: 940px;
  --fd-sidebar-width: 320px;
  --fd-toc-width: 240px;
}

See Styling for the full token list.