Configuration
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.
/** @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 sidebar follows the file tree. The only value written so far |
globalComponents | true | Shipped components available without an import |
scrollToTop | true | Back-to-top button on every page |
jsonld | { enabled: true } | Structured data |
lang | 'en' | Language of the document, in <html lang>. The shell's labels stay English |
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 |
Images
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.
logosits beside the project name, at the height of the header's text: a square image reads best there. Itsaltstays empty, since the name follows it. It is also the logo of the organisation in the structured data.faviconis the icon of the browser tab:.ico,.pngor.svg.socialImageis 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, hencesiteUrl.
A declared image that does not exist stops the build, naming the field.
versions
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 |
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 |
theme
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 | Dark theme strategy |
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.
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.
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.
DocPensieve