# Configuration {#configuration}



## The site {#the-site}


```ts
import { defineConfig, markdown } from "sourcey";

export default defineConfig({
  name: "Cheese Store",
  siteUrl: "https://docs.cheese.example",
  prettyUrls: "slash",
  repo: "https://github.com/cheese/docs",
  editBranch: "main",
  editBasePath: "docs",
  navigation: {
    tabs: [{ tab: "Documentation", slug: "", source: markdown({ groups: [{ group: "Start", pages: ["introduction"] }] }) }],
  },
});
```

`navigation.tabs` is the one setting a site needs; [Navigation](https://sourcey.com/docs/config/ref-navigation.md) describes its tabs, groups and pages. `name` is shown beside the logo and ends each page's title, after the page's own title and `titleSeparator` (` - ` by default). `siteUrl` is the site's public origin, which its canonical URLs, social cards, language alternates and sitemap name; `baseUrl` is the path it is served under when that is not the origin's root, such as `/docs`. `prettyUrls` chooses how pages are written and linked: `false`, the default, writes `guide.html` and links it as written; `"slash"` writes `guide/index.html` and links `/guide/`; `"strip"` writes `guide.html` and links `/guide`, for a host that serves a page without its extension.

`repo`, `editBranch` and `editBasePath` give each page its "Edit this page" link: the repository's address on GitHub, its branch, and the folder within it that holds the config, when that is not the repository's root.


### Social cards {#social-cards}


Each page gets a social card drawn when the site builds, with its title, its description and the site's logo and colours. `ogImage: "./social.png"` uses one image for every page instead: a local path is published with the site, relative to the config, and an address is used as given.


## Documentation exports and discovery {#documentation-exports-and-discovery}


Markdown views, `documents.json`, `llms.txt` and `llms-full.txt` are generated by default. Control them with `exports`; opt into public source contracts and discovery with:

```ts
exports: { markdown: true, llms: { index: true, full: true }, openapi: true },
discovery: { ard: true, apiCatalog: true },
```

Discovery requires `siteUrl`. A build mounted beneath another website emits scoped manifests; `wellKnown: true` is reserved for a build that owns the origin root. [Full configuration and deployment guide](https://sourcey.com/docs/context-exports.md).

Sourcey reads `sourcey.config.ts` from your project and keeps the structure explicit. Each tab has one `source`, usually created with `markdown()`, `openapi()`, `mcp()`, `doxygen()`, `godoc()`, or `rustdoc()`.

```ts
import { defineConfig, doxygen, godoc, markdown, mcp, openapi, rustdoc } from "sourcey";

export default defineConfig({
  name: "My API",
  navigation: {
    tabs: [
      {
        tab: "Docs",
        slug: "",
        source: markdown({
          groups: [
            {
              group: "Getting Started",
              pages: ["introduction", "quickstart"],
            },
          ],
        }),
      },
      {
        tab: "API Reference",
        slug: "api",
        source: openapi("./openapi.yaml"),
      },
      {
        tab: "MCP Reference",
        slug: "mcp",
        source: mcp("./mcp.json"),
      },
      {
        tab: "C++ Reference",
        slug: "cpp",
        source: doxygen({
          xml: "./build/doxygen/xml",
          language: "cpp",
          index: "rich",
          sourceUrl: [
            { prefix: "third_party/private/" },
            { prefix: "", url: "https://github.com/acme/project/blob/main/{fullPath}" },
          ],
        }),
      },
      {
        tab: "Rust API",
        slug: "rust-api",
        source: rustdoc({
          manifest: "../crates/Cargo.toml",
          crates: ["my-crate", "my-other-crate"],
          snapshot: "./snapshots/rustdoc.json",
          mode: "auto",
          features: { list: ["full"] },
          sourceBasePath: "crates",
          doctestsIndex: true,
        }),
      },
    ],
  },
});
```


## Sources {#sources}


Each tab reads one source:

| Source | Reads | See |
| --- | --- | --- |
| `markdown({ groups })` | Markdown pages | [Navigation](https://sourcey.com/docs/config/ref-navigation.md) |
| `openapi(spec)` | An OpenAPI 3.x or Swagger 2.0 spec, a file or a URL | [OpenAPI](https://sourcey.com/docs/openapi-integration.md) |
| `mcp(spec)` | An MCP server snapshot | [MCP](https://sourcey.com/docs/mcp-integration.md) |
| `doxygen(config)` | Doxygen XML for C, C++ or Java | [Doxygen guide](https://sourcey.com/docs/guides/guide-doxygen-cpp.md) |
| `godoc(config)` | A Go module, live or from a snapshot | [Go guide](https://sourcey.com/docs/guides/guide-godoc-go.md) |
| `rustdoc(config)` | Rust crates, live or from a snapshot | [Rustdoc adapter](https://sourcey.com/docs/rustdoc-integration.md) |


### doxygen() {#doxygen}


`doxygen().sourceUrl` accepts a base URL string, a resolver function, or a
route map. Route maps use longest-prefix matching. `{path}` expands to the path
after the matched prefix, `{fullPath}` expands to the original Doxygen path,
and `{line}` expands to the source line. A route without `url` suppresses the
public link while generated pages still show `Defined in path:line`.

`doxygen().language` is `"cpp"`, the default, for C and C++, or `"java"`.
Each group, namespace and type gets a page, and the functions, variables and
macros at global scope that no group claims share a Globals page beside the
overview. A namespace that only wraps others gets no page of its own.


## Logo and favicon {#logo-and-favicon}


```ts
logo: { light: "./brand/light.svg", dark: "./brand/dark.svg" },
favicon: "./brand/favicon.svg",
```

Local images are published once as content-addressed files shared across pages. Browsers can cache them while readers move through the site; the HTML contains their URLs. Editing an image updates its URL and live preview automatically. The same frozen bytes feed generated social images. HTTP URLs and explicitly authored data URLs remain supported; a social image fetches a logo named by URL once per build. Social images are dark, so they draw the `dark` logo when one is set, at its own proportions.

The logo is taken as a full wordmark, so the site's `name` is not repeated beside it. For a mark without the name, set `logo: { light: "./brand/mark.svg", mark: true }` and the name shows beside it in every theme and in social images.

Without a `favicon`, a site's icon is its logo where that is a mark, or else a square of its primary colour, as its social images draw beside the name. A wordmark is too wide to be an icon.


## Navbar {#navbar}


```ts
navbar: {
  links: [
    { type: "link", href: "https://example.com/integrations", label: "Integrations" },
    { type: "menu", label: "Community", links: [
      { type: "link", href: "https://forum.example.com", label: "Forums" },
      { type: "discord", href: "https://discord.gg/example" },
    ] },
    { type: "github", href: "https://github.com/example/docs" },
  ],
  primary: { type: "button", label: "Sign in", href: "https://app.example.com" },
},
```

`navbar.links` sit at the header's end: a network's icon (`github`, `npm`, `twitter` for X, `discord`, `discourse`, `slack`, `linkedin`, `youtube`, `mastodon`, `bluesky` or `reddit`, its `label` the icon's accessible name, the network's own name by default), a text link (`link`) with its `label`, or a `menu`, links under one label that open below it, one menu at a time. A menu holds links only, one level deep; the mobile drawer lists them under its label. `navbar.primary` is the call to action, a button after the links and at the foot of the drawer.

A `github` link to a repository can show its latest release, stars and forks beside its mark: `{ type: "github", href: "https://github.com/example/docs", stats: true }`. The build reads them from GitHub's API, two requests per build, so they are as fresh as the site's last build and no reader's browser calls GitHub. Give the build `GITHUB_TOKEN` (in GitHub Actions, `env: { GITHUB_TOKEN: ${{ github.token }} }`) for GitHub's higher limit. The build reads them while the pages load; when GitHub does not answer within three seconds, the build warns and the link shows without them.


## Breadcrumbs {#breadcrumbs}


```ts
navigation: {
  breadcrumbs: true,
  tabs: [/* ... */],
},
```

Each documentation and reference page then opens with the way to it above its title: the site's home, its tab where the site has several, and each section the page sits in, every step a link where the section has a page of its own. The page itself is not a step, since its title follows. The trail is drawn when the site builds, wraps on a phone, and is the same trail search engines read from the page's structured data. Off by default; without it, each theme names the page's section as it always has.


## Footer {#footer}


```ts
footer: {
  text: "© Example Ltd. Licensed under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/).",
  links: [{ type: "github", href: "https://github.com/example/docs" }],
},
```

`footer.text` is one line of inline Markdown at the foot of every page, such as a copyright or licence notice; the reader theme shows it in place of the site name unless `reader.footer.text` is set. `footer.links` take the same form as `navbar.links`: a network's icon, or a text link with its `label`. The footer sets text links first and the networks' icons together after them. Keep it to places the header and sidebar do not already lead: the site's own pages are in the sidebar, and its repository is usually in the header. "Edit this page" is the page's own: it ends each page, above previous and next, and opens the page's file on GitHub, which anyone can read and whoever can edit opens in GitHub's editor from there. A page edited somewhere other than the site's repository names its own address in frontmatter, `editUrl: https://github.com/example/agent/edit/main/docs/guide.md`, and `editUrl: false` leaves the link off.


## Last updated {#last-updated}


```ts
lastUpdated: true,
```

Each documentation page and changelog shows when it last changed, beside "Edit this page": the day of the last commit to its file, in the page's language. The sitemap names the same date as the page's `lastmod`. A file no commit holds yet has no date. Off by default.

The build reads the repository's history, so a CI checkout needs all of it. A shallow checkout stops the build, since it would date every page by its one commit:

```yaml
- uses: actions/checkout@v7
  with:
    fetch-depth: 0
```


## Page feedback {#page-feedback}


```ts
feedback: true,
```

Each documentation, reference and changelog page then ends by asking "Was this page helpful?" in the page's language, answered with a thumbs up or a thumbs down from the site's icon set. Choosing an answer shows its note and dispatches a `sourcey:feedback` event from the question, with the answer's `value` and the page's path. Sourcey sends the answer nowhere itself; the site's own script, in its [`Head` component](https://sourcey.com/docs/components.md#page-parts), sends it where the site keeps feedback. The question stays hidden until the page's script runs, so a reader without JavaScript sees no buttons that do nothing. Frontmatter `feedback: false` leaves it off a page. Off by default.

```html
<!-- components/Head.html -->
<script>
  document.addEventListener("sourcey:feedback", (event) => {
    navigator.sendBeacon("https://example.com/feedback", JSON.stringify(event.detail));
  });
</script>
```

A site can ask its own question. Each rating has a `label`, the `value` it sends, an `icon` from the site's icon set (without one, the label is the button) and a `note`, in Markdown:

```ts
feedback: {
  title: "Did this page help?",
  ratings: [
    { label: "It helped", value: 1, icon: "face-smile", note: "Thanks!" },
    { label: "It could be better", value: 0, icon: "face-frown", note: "Tell us [on GitHub](https://github.com/example/docs/discussions)." },
  ],
},
```


## Typography {#typography}


```ts
markdown: { smartypants: true },
```

`markdown.smartypants` sets prose with typographer's punctuation: straight quotes become curly (`"quoted"` reads “quoted”, `it's` reads it’s), `--` becomes an em dash and `...` an ellipsis. It applies to guide pages and to the Markdown in references, such as descriptions. Code, preformatted text, page titles and navigation labels keep every character as written, as do the Markdown and agent exports, and heading anchors do not change. It is off by default; `sourcey migrate astro` turns it on, because Astro sets Markdown this way unless its `markdown.smartypants` is false.


## Themes {#themes}


`theme` chooses one of four themes and sets its colours, fonts, layout widths, light and dark appearance, stylesheets and icons. See [Themes](https://sourcey.com/docs/themes-and-layout.md).


## Interactive requests {#interactive-requests}


OpenAPI references include an inline request console by default. Set `tryIt: false` to disable it site-wide or on one tab. Use `tryIt: { credentials: "include" }` on a tab that needs browser-managed sessions across origins. The default is `same-origin`; `omit` disables session credentials.

See [Try it](https://sourcey.com/docs/try-it.md) for authentication, local drafts, browser requirements, and global CSS overrides.


## Code samples {#code-samples}


`codeSamples` chooses the languages of each operation's request examples, in the order its picker shows them. See [Code samples](https://sourcey.com/docs/openapi-integration.md#code-samples).


## Search {#search}


`search.featured` names the pages search offers before anything is typed. See [Search](https://sourcey.com/docs/search.md).


## Publishing checks {#publishing-checks}


`sourcey build` and `sourcey check` report what publishing needs: a `siteUrl`, without which pages name no canonical URL or language alternates and the site has no sitemap, as each needs absolute URLs; pages with no summary of their own, or a one-word one (their metadata falls back to the title); canonical or language links to no published page; and links to a fragment the target page does not have. Each kind is reported once, with the pages it affects. With a `siteUrl`, every page carries its place in the navigation as schema.org `BreadcrumbList` data, and its description is plain text drawn from its own summary.


## Documentation releases and languages {#documentation-releases-and-languages}


[Versions](https://sourcey.com/docs/versions.md) declares current and historical content roots, independent API releases and their URLs. [Languages](https://sourcey.com/docs/languages.md) covers existing translations, fallback pages, direction and UI messages. These settings use the same native sources and navigation as a single-language site.


## Public files {#public-files}


Set `publicDir: "./public"` to copy an explicitly public directory to the output root with its paths intact. A file at `public/files/manual.pdf` is served at `/files/manual.pdf`, or beneath the configured `baseUrl`. Public files participate in development updates and output collision checks. A public file cannot replace a generated page or asset.

For Markdown-relative images and downloads, Sourcey publishes the referenced bytes automatically. Use `publicDir` for paths that must stay fixed. Keep source code, credentials and private inputs outside that directory. In an Astro project, Astro continues to own its own `public/`; do not configure both tools to publish the same files.


## Site pages {#site-pages}


Set `pages: "./pages"` for the site's own pages, outside the documentation: a home page, a product or pricing page, a page of your own design. Each publishes at the address its path names, `pages/index.md` at the site root and `pages/pricing.md` at `/pricing/`. A site page belongs to no tab and no version, so no section is current on it and no sidebar lists it. Names starting with `_` or `.` are partials, not pages.

Site pages are written in the same Markdown, with the same components, as documentation. They take frontmatter `title` and `description`, are searched and exported like any page, and link operations by name. In a translated site each language keeps its own under its content folder (`pages/fr/` beside `pages/`), and with fallback on the default language's page stands in for one a language lacks.


## Page layouts {#page-layouts}


A page's frontmatter `layout` says how it is drawn:

| `layout` | Drawn with |
| --- | --- |
| `doc` | The documentation frame: header, sidebar, contents and footer. The default for documentation. |
| `page` | The site's header and footer around the page's own body, with no sidebar or contents: its text at a reading measure, its HTML the page's full width. The default for site pages. |
| `blank` | Nothing but the page's body. Sourcey keeps the page's `<head>`: its title, description, social card, theme colours and fonts. |
| `changelog` | A changelog, as for `CHANGELOG.md`. |

The site's own head markup, banner, header, footer and the end of each page's content come from its [components folder](https://sourcey.com/docs/components.md#page-parts). A `page` or `blank` page draws its own heading and opening; Sourcey adds no title above them. Prose keeps the theme's typography; wrap a block in `<div class="not-prose">` to style it yourself. A page can bring its own stylesheets with frontmatter `css: ./home.css` (or a list), resolved from the page's folder: Sourcey publishes each once, with its relative fonts and images, and links it from that page alone.

```md
---
title: Pets for everyone
description: Adopt, foster and care for a pet.
layout: blank
css: ./home.css
---

<section class="hero">
  <h1>Pets for everyone</h1>
</section>
```


## Broken links {#broken-links}


Each build reports a link to a path on the site that no page, file or redirect answers, beside links to a heading a page does not have. Set `brokenLinks: "error"` to have `sourcey build` stop instead, naming each link and writing nothing, as Docusaurus does by default; the dev server reports them either way, so a page being written can link ahead. A site published inside a host, such as an Astro site with pages of its own, reports none, since the host may answer them.


## Root and missing pages {#root-and-missing-pages}


A page that publishes at the site root, `pages/index.md` or a documentation page at `/`, is the home page, and the logo leads there. When nothing publishes there, a standalone build redirects the root to the first section's first page.

Sourcey generates `404.html` for standalone output; `pages/404.md` replaces it with a page of your own. A not-found page names its assets and links from the site root, because the host serves it at any address, and marks its root `<html data-sourcey-page="not-found">`, so analytics in the site's [`Head` component](https://sourcey.com/docs/components.md#page-parts) can count missing pages. Configure your static host to use that file with an HTTP 404 status. An HTML file alone cannot set a response status. Use `notFound: false` when the host already owns its missing-page route.


## Redirects {#redirects}


Use absolute public paths, including your `baseUrl`:

```ts
redirects: {
  "/docs/old-guide": "/docs/installation/",
  "/docs/preview": { to: "/docs/installation/", status: 302 },
}
```

A string declares permanent 301 intent. Explicit statuses can be 301, 302, 303, 307 or 308. Local destinations must exist; fragment targets, cycles and collisions with content are checked during compilation. Wildcard and query-matching rules are not supported.

Standalone builds emit HTML fallback pages and a `_redirects` file for hosts that support that format. An authored public `_redirects` file cannot also own those rules. Static HTML does not issue an HTTP redirect status. Its replacement script keeps an incoming query and fragment unless the destination supplies its own; without JavaScript, the meta refresh and link use the exact configured destination. Dev issues the declared status and retains the incoming query when the destination has none. Astro registers the same records with its host router; the adapter and hosting platform determine production HTTP handling.

`sourcey check` compiles the same inputs and checks routes, imported reference fragments and output collisions without writing a site. Normal builds perform those checks too.


## Reference pages {#reference-pages}


An OpenAPI or MCP reference publishes in one of three shapes:

| `reference.split` | Publishes |
| --- | --- |
| `false` | One page holding everything |
| `"sections"` | A page for each section, an OpenAPI tag or MCP group, holding its operations in full |
| `"operations"` | A page for each operation |

Unset, Sourcey chooses by size and says which it chose when it builds. A page holds at most 50 operations and 1,000 field rows, counting each parameter, body field and response a page draws. A reference within that is one page. A larger one with several sections, each within it, gets a page per section: an MCP server divided into groups usually does, as do APIs with modest, light tags. Otherwise each operation gets its own page, as a large API, or one whose operations carry large schemas, needs. Field rows matter as much as operations: a hundred light tools read well a section at a time, while thirty operations returning large schemas make a page too heavy to open.

A split reference also publishes a page for each model, beside an overview: split by section, it lists the sections, each with its description or the operations it holds; split by operation, it lists every operation under its section. Every shape uses the same reference components, examples and schema links, and OpenAPI operations keep their request console. On a split reference's overview, each operation and model keeps the anchor its section has on the single page, so a link from before the split lands on its entry, and an operation's own address leads wherever its shape puts it, so a reference can grow from one shape to another without breaking a link. Each theme frames those pages as it frames a guide: the section's name over an operation's title, previous and next in navigation order, and an overview at reading width whose "On this page" lists its introduction's headings, as the sidebar lists the sections. A reference split by section with only one section to split by stays one page, and the build says so; one held on one page with more than 100 operations builds with a warning, as it is slow to open and to search.

An MCP server's sections are its kinds, Tools, Resources and Prompts, unless its `groups` divide it as its authors do; see [Sections](https://sourcey.com/docs/mcp-integration.md#sections).

Schema alternatives use a native picker, labelled from their titles, reference names or discriminator values. Without JavaScript every alternative remains available. A field link opens the matching alternative and its parent disclosures; readable Markdown retains all alternatives. Typed map values use the same field rows as named properties, including descriptions, constraints and nested values.

A named type never opens inside itself: a field of the type it sits in reads "Same fields as" and links up to where that type is drawn. A schema that would draw more than 500 field rows, as large Kubernetes types do, draws each named type once, at its shallowest place, and its other fields link there. Generated examples follow the same rule: a property that would repeat its own type is left out (empty where it is required), and an example longer than 400 lines shows each named object type once.

Set `reference.operations` to give every operation a permanent address. `{operation}` uses the authored operation ID, or the path when no ID exists. `{operationId}` requires an ID; `{method}` and `{path}` are also available. Values use the native heading slug rules. A reference split by operation publishes each operation's page there; one page, or a page per section, redirects the address to the operation where it is, so the reference can change shape without breaking a link. Explicit tab `routes` for operations and models work the same way and win over the pattern. Duplicate physical paths fail the build.

Link an operation from any page by name, as navigation names it: `[List keys](<operation:GET /keys>)`, or `operation:TOOL search` for an MCP tool. The link lands wherever the operation is published, on its page or its section, and follows a split. When two references publish the same operation, name the reference's tab ID: `operation:rest:GET /keys`. A link to an operation no reference publishes fails the build. Cards take the same `href`.

A converter can retain previous fragment spellings with `anchors: { "POST /items": { "old-body": "request-body" } }`. Each alias must target a real section or field on that document. It is placed beside that content, so an old link reaches the field, including inside a disclosure. Field identities follow the owning operation or schema, property path, media type and response status; they stay distinct across repeated names and remain stable between reference layouts. Converters write the original spellings as data, and Sourcey checks their destinations during compilation.

Missing alias destinations fail the build by default. Set `anchorUnmatched: "warn"` on the tab for imported link history that follows a changing spec: Sourcey reports removed documents or fields and omits only their stale aliases. `"ignore"` omits them silently. Invalid alias spellings and collisions remain errors under every policy. The public build result exposes warnings as `anchorDiagnostics`.

Large generated navigation, route and anchor tables can live in JSON files imported by the config. Use a default import (`import anchors from "./api.anchors.json"`); Sourcey loads the file as data and tracks it for live reload. This avoids compiling a large table into thousands of JavaScript exports.


### Choose the reference order {#choose-the-reference-order}


Generated navigation has a group per tag, in the spec's order, labelled by the tag's `summary` or name. A tag with a `parent` (OpenAPI 3.2) sits inside its parent's group, after the parent's own operations; an overlay can add that hierarchy to an older spec. A tag with nothing to list beneath it is left out.

Give a tab its own navigation tree when the generated groups do not match how people use the API:

```ts
{
  id: "rest",
  tab: "API Reference",
  source: openapi("./openapi.json"),
  reference: { split: "operations" },
  routes: { "GET /keys": "keys/list" },
  navigation: [
    { group: "Start here", pages: [{ page: "introduction" }] },
    {
      group: "Keys",
      pages: [{ operation: "POST /keys" }, { operation: "GET /keys" }],
    },
  ],
},
```

Operation entries use `METHOD /path`, exactly as the spec defines it. Their labels come from the effective spec's `summary`; `label` overrides the navigation text. Sourcey resolves each entry to its operation page, or its section on a single page. Missing and unpublished targets fail the build.

Markdown groups can also link to a reference owned by another tab with `{ operation: "GET /keys", reference: "rest" }`. Use a stable tab `id` for `reference`. A page in another source uses `{ page: "installation", tab: "guides" }`; the page can be named by its source reference or stable document ID. Cross-source links select the matching language and content version. Readers follow the canonical document in its owning tab.

The source controls which operations are published. Navigation controls their order and grouping. An operation can appear in several groups while keeping one URL. The sidebar lists every section, with the one around the current page open; a section of more than 80 entries without a page of its own gets a generated page listing them, which its link leads to where it is folded. Navigation data can live in a separate TypeScript file, typed with `GroupConfig[]`, and be imported by the config.
