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.
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.
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).
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 |
Navbar and footer
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.
Link previews
| 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.
Sidebar
| 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.
Source links
| 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).
Sidebar
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:
:root {
--fd-content-max: 940px;
--fd-sidebar-width: 320px;
--fd-toc-width: 240px;
}See Styling for the full token list.