# Configuration

> Every field of docpensieve.config.mjs, its default value and its effect.

The `docpensieve.config.mjs` file, at the root of the project.

It is an ES module — hence `.mjs`, which Node reads as one whatever the
project's `package.json` says. `docpensieve.config.js` works too, in a project
whose `package.json` declares `"type": "module"`; with both files present, the
build stops rather than pick one.

```js
/** @type {import('@docpensieve/core').DocPensieveConfig} */
export default {
  projectName: 'My documentation',
  siteUrl: 'https://example.com/my-project',

  versions: [{ slug: 'v1.0', name: '1.0', folder: 'docs/v1.0', current: true }],

  outDir: 'dist',

  theme: {
    framework: 'tailwind',
    darkMode: 'class',
  },

  sidebar: 'auto',
  globalComponents: true,
  jsonld: { enabled: true },
};
```

The `@type` comment gives autocompletion and type checking in the editor
without importing anything: the file stays readable even where DocPensieve
only runs through `npx`. In a project that installs it as a dependency,
`defineConfig` from `@docpensieve/core` does the same.

`docpensieve init` writes this file with **every field** in it — set to its
default, or commented out with an example — so that it also tells you what you
can change.

## The fields

| Field              | Default             | Effect                                                                                             |
| ------------------ | ------------------- | -------------------------------------------------------------------------------------------------- |
| `projectName`      | `'Documentation'`   | Name shown in the header and in the JSON-LD                                                        |
| `siteUrl`          | `''`                | Public URL. Used for the `canonical` and the JSON-LD                                               |
| `baseUrl`          | `'/'`               | Deployment prefix. Derived from `siteUrl` when omitted                                             |
| `outDir`           | `'dist'`            | Output folder, relative to the root                                                                |
| `versions`         | `[]`                | At least one entry                                                                                 |
| `theme`            | see below           | Styling                                                                                            |
| `sidebar`          | `'auto'`            | `'auto'`: the menu follows the file tree. Or a `.json` file of each version folder                 |
| `authors`          | `''`                | A `.json` file describing the authors, in each version folder that has one                         |
| `headerLinks`      | `[]`                | Links of the header, beside the version switcher                                                   |
| `foldedSidebar`    | `false`             | Categories of the menu fold, opened where the reader stands                                        |
| `admonitions`      | `{}`                | Kinds of admonition the project adds to the six shipped, each a label, a tone and an optional icon |
| `stickyHeader`     | `true`              | The header stays at the top of the screen; `false` lets it scroll away                             |
| `globalComponents` | `true`              | Shipped components available without an import                                                     |
| `scrollToTop`      | `true`              | Back-to-top button on every page                                                                   |
| `copyCode`         | `false`             | A button copying each block of code — the one field that makes a content page load a script        |
| `snippetIcons`     | `''`                | Icon collection a code block takes the mark of its language from, `'simple-icons'`                 |
| `jsonld`           | `{ enabled: true }` | Structured data                                                                                    |
| `lang`             | `'en'`              | Language of the site: `<html lang>`, and the wording of the shell                                  |
| `ui`               | `{}`                | Wording of the shell, by language — corrects a word, or adds a language                            |
| `logo`             | `''`                | Image beside the project name, in the header                                                       |
| `favicon`          | `''`                | Icon of the browser tab: `.ico`, `.png` or `.svg`                                                  |
| `socialImage`      | `''`                | Preview of a shared page. Needs `siteUrl`                                                          |
| `sitemap`          | `true`              | `sitemap.xml` of the published versions, once `siteUrl` is set                                     |
| `feed`             | `false`             | RSS feed of the dated pages. Needs `siteUrl`                                                       |
| `search`           | `true`              | Search field in the header, and a search page built with the site                                  |
| `llms`             | `false`             | `llms.txt` at the root of the site, and a Markdown copy of each page                               |

## Images

```js
logo: 'branding/logo.png',
favicon: 'branding/favicon.png',
socialImage: 'branding/social.png',
```

The paths start from the project root. Each image is copied into every
version, under `assets/`, so that a version stays whole on its own branch.

- `logo` sits beside the project name, at the height of the header's text: a
  square image reads best there. Its `alt` stays empty, since the name follows
  it. It is also the logo of the organisation in the structured data.
- `favicon` is the icon of the browser tab: `.ico`, `.png` or `.svg`.
- `socialImage` is what a social network shows of a shared page, with its
  title and description. 1200 × 630 pixels is the usual size, and SVG is not
  read there. Those networks only read an absolute address, hence `siteUrl`.

A declared image that does not exist stops the build, naming the field.

## Sitemap and feed

```js
siteUrl: 'https://example.com',
sitemap: true, // the default
feed: true,
```

Once `siteUrl` is set, the build writes `sitemap.xml` at the root of the site:
every page of every version, except a version in preparation, whose pages
carry `noindex`. Each page is dated by its `modified` frontmatter, or failing
that its `date`. `sitemap: false` turns it off.

`robots.txt` joins it when the site is served at the root of its domain.
Search engines only read that file there: under a sub-path, it would be
written for nobody — declare the sitemap to them directly.

`feed: true` writes `feed.xml`, an RSS feed of the pages of the current version
that carry a `date`, newest first, and every page announces it in its head.
It is off by default: most documentation pages carry no date.

Both list absolute addresses: asked for without `siteUrl`, they stop the build.

## Search

`search: true`, the default, puts a search field in the header and builds a
search page in each version, at `/search/`. The build writes the index of the
version — the plain text of every page — and the search page already lists
every page with its description.

The field is a plain form that leads to that page: it needs no script. The
search page loads one of its own, of a few kilobytes, which filters
the list as you type, best matches first, with an excerpt of each. Without
JavaScript, the page stays the full list of pages.

The search page is kept out of search engines (`noindex`) and out of the
sitemap. A page of your own at `/search/` would take its place: the build
refuses it, and `search: false` frees the address.

## `llms`

```js
llms: true,
```

A program that reads documentation on someone's behalf — an assistant
answering a question, a tool gathering the pages of a project — does better
with the text than with the markup around it. `llms: true` gives it two ways
in:

- **`llms.txt`**, at the root of the site: the project name, the description
  of the home page, then one link per page of the current version, with its
  description. Each language of the version has its own section. The file
  follows the llms.txt proposal, at the address programs look for it.
- **A Markdown copy of each page**, beside its HTML: `/guide/install/` has its
  copy at `/guide/install/index.html.md`, and every page names its copy in its
  head, with `<link rel="alternate" type="text/markdown">`. The copy is the
  source of the page as you wrote it, headed by its title and description.

What a component draws at build time is not in the copy — a grid of cards,
the file a snippet reads: it says what was written, not what was rendered.

It needs no `siteUrl`: the links start from the root of the domain until the
site has an address, and are absolute once it has one. Like the sitemap,
`llms.txt` is written by the build of the whole site; `build <version>` alone
writes the copies of that version only.

It is off by default. It doubles the files of a version, and whether programs
should read your pages is yours to decide. It is a proposal, not a standard:
reading it is up to each program.

## `copyCode`

`copyCode: true` puts a button on every block of code, which copies it.

It is off by default, because it is the one field that makes an ordinary page
load a script. Turned on, the behaviour is written **once per version**, in
`assets/client.js` beside the stylesheet, and shared by every page and every
language — a reader who has it in cache pays for it once. Only the pages that
hold a block of code load it; the others go on loading nothing.

About two kilobytes, compressed. No framework, no hydration: the code is plain
DOM, and the button is drawn by the script itself. Without JavaScript the
block is exactly what it was, text you can select — nothing is missing but
the button.

The label follows the language of the page, in English and in French.

## `versions`

```js
versions: [
  { slug: 'v2.0', name: '2.0', folder: 'docs/v2.0', current: true },
  { slug: 'v1.0', name: '1.0', folder: 'docs/v1.0', archived: true },
],
```

| Field          | Role                                                                                                                           |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `slug`         | URL and branch identifier. A number (`v1.0`) freezes the address; a channel (`latest`, `beta`) keeps it right as versions move |
| `name`         | Label shown in the switcher                                                                                                    |
| `folder`       | Source folder, relative to the root                                                                                            |
| `current`      | Version served by default. At most one                                                                                         |
| `archived`     | Version kept but no longer maintained. Banner, but stays indexed                                                               |
| `prerelease`   | Version in preparation. Banner **and** `noindex`                                                                               |
| `logo`         | This version's logo, instead of the project's — a beta told apart at a glance                                                  |
| `favicon`      | This version's favicon, instead of the project's                                                                               |
| `translations` | Folder of each translation, by language code: `{ fr: 'docs/v1.0-fr' }`                                                         |

## `theme`

```js
theme: {
  framework: 'tailwind',
  darkMode: 'class',
  tokens: { '--dp-accent': 'oklch(55% 0.2 250)' },
  css: '.dp-article h2 { letter-spacing: -0.01em; }',
  source: '@import "tailwindcss";',
},
```

| Field       | Effect                                                                                                                              |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `framework` | `'tailwind'` or `'custom'`                                                                                                          |
| `darkMode`  | `'class'` follows the reader's system; `'dark'` or `'light'` keeps one scheme                                                       |
| `toggle`    | On by default: a light / dark button in the header, remembered from page to page — a few lines of inline script. `false` removes it |
| `tokens`    | Redefined `--dp-*` tokens                                                                                                           |
| `css`       | CSS appended to the produced stylesheet                                                                                             |
| `source`    | Stylesheet handed to the utility compiler                                                                                           |

The available tokens are listed in [Themes](/versions/beta/guide/themes/).

Longer rules go in the `theme/` folder, at the root of the project: every
`.css` file in it is appended after `css`, in name order, and
`docpensieve dev` picks up every change. Under the `custom` theme, `init`
starts it with `theme/custom.css`.

## `authors`

```js
authors: 'authors.json',
```

Names a JSON file **read in each version folder**, describing the authors a
page names in its frontmatter: a name, a biography, an avatar, a link.

Empty by default, and then a page still shows the names it gives, without the
rest: the file enriches, it does not command. A version without the file shows
the names alone, so describing the authors of a new version does not force a
copy into the older ones. A file present but unreadable, or wrongly written,
stops the build.

The file is not published. The avatars are, their path starting at the version
folder so that they travel with it.

## `headerLinks`

```js
headerLinks: [
  { label: 'Blog', href: '/blog/' },
  { label: 'Examples', href: '/examples/', version: 'beta' },
  { label: 'Repository', href: 'https://github.com/me/my-project' },
],
```

Links of the header, beside the version switcher. A target starts from the root
of the version — `/blog/` — or is a full address. A relative target is refused:
the header is on every page, and `blog/` would mean something else on each.

`version` names the version a link leads to. Without it, each version links to
its own page; with it, every version leads to that one — which is how a section
written in one version only stays reachable from all of them. A version nobody
declared stops the build.

On a narrow screen, the version switcher, the links and the search field move
behind a menu button. It is a native element that opens without a script, like
the version switcher itself.

### A panel of links

An entry carrying `columns` opens a panel instead of leading anywhere:

```js
headerLinks: [
  {
    label: 'Product',
    columns: [
      {
        title: 'Guide',
        items: [
          { label: 'Install', href: '/guide/installation/' },
          { label: 'Deploy', href: '/guide/deployment/' },
        ],
      },
      { items: [{ label: 'Repository', href: 'https://github.com/me/my-project' }] },
    ],
  },
],
```

A column may go without a title. Every link follows the rules above — absolute
target, optional `version`. An entry cannot carry both `href` and `columns`: it
either leads somewhere or opens a panel, never both.

The panel is declared here, not derived from the menu of the documentation: the
two can stand side by side with different links, or the site can go without a
sidebar and navigate from the header alone.

## `stickyHeader`

```js
stickyHeader: false,
```

The header holds to the top of the screen by default: the search field, the
version switcher and the menu stay in reach wherever the reader is in the page.

`false` lets it scroll away with the page, which gives its height back to the
text — on a phone held in one hand, that height is a third of what is visible.
The anchors then stop reserving room for it: a link to a heading no longer
leaves a blank band above it.

## `foldedSidebar`

```js
foldedSidebar: true,
```

Every category of the menu becomes a fold. The branch holding the page being
read is open, the others closed: a menu of a hundred pages stops asking the
reader to scroll past what does not concern them. It folds without a script.

A category that is also a page gains that page as its first entry: the handle
of a fold cannot be a link as well without a click meaning two things.

Left to `false`, the menu shows whole, as before. On a narrow screen the whole
menu folds above the content either way, since the column has nowhere to stand.

## `sidebar`

`'auto'` builds the menu from the file tree: folders become categories, and the
`01-`, `02-` prefixes set the order. To write it by hand, name a JSON file:

```js
sidebar: 'sidebar.json',
```

It is read from **each version's folder** — `docs/v1.0/sidebar.json` — since
each version has its own pages. A version without the file keeps the menu of
its folders, so a menu written for a new version needs no copy in the older
ones. The file holds an array of entries, kept in the order written:

```json
[
  "/",
  { "label": "Guide", "page": "guide", "items": ["guide/installation", "guide/first-site"] },
  { "page": "reference/cli", "label": "Commands" },
  { "auto": "docpensieve" },
  { "label": "Repository", "href": "https://github.com/me/my-project" }
]
```

| Entry                           | What it gives                                                                   |
| ------------------------------- | ------------------------------------------------------------------------------- |
| `"guide/installation"`          | A page, by its path within the version, as in its URL — `"/"` for the home page |
| `{ "page", "label" }`           | The same page, with a label of its own                                          |
| `{ "label", "items", "page"? }` | A category, clickable when it names a page                                      |
| `{ "label", "href" }`           | A link outside the site                                                         |
| `{ "auto": "folder" }`          | The automatic menu of a folder: the DocPensieve section keeps its own this way  |

A page left out stays published: it is only absent from the menu. A path that
names no page, a page listed twice, or an entry of no known kind stops the
build, naming the file and the paths close to the one written. The file itself
is not published.

## `baseUrl`, and why you rarely write it

A `siteUrl` with a sub-path already gives it: `https://example.com/my-project`
produces `baseUrl: '/my-project/'`. Writing it is only useful to depart from
it.

The prefix is normalised with both its slashes. It is what prefixes every
internal link: if it is wrong, every link is.

## What is refused

| Case                               | Message                           |
| ---------------------------------- | --------------------------------- |
| The configuration is not an object | An example of the expected export |
| No version declared                | An example of a complete entry    |
| Two versions with the same slug    | The offending slug                |
| Several `current` versions         | The list of those found           |
| Unknown `framework`                | The accepted values               |
| Requested slug not found           | The available slugs               |
| An image of the wrong kind         | The accepted extensions           |
| `socialImage` without `siteUrl`    | Why the address is needed         |
| A declared image that is missing   | The field and its path            |

Each one stops the build with a message and a hint, without a stack trace.

## What is filled in automatically

If **no** version carries `current`, the first in the list becomes current. A
single-version configuration therefore has nothing to specify — this field only
starts to matter from the second version on.
