# Configuration reference
`markbook.config.{ts,mts,js,mjs}` lives at your project root and exports a `MarkbookConfig` object via `defineConfig` (for type inference).
```ts
import { defineConfig } from '@doidor/markbook-core';
export default defineConfig({
// ...
});
```
## Project layout
| Field | Type | Default | Purpose |
| --- | --- | --- | --- |
| `root` | `string` | `process.cwd()` | Project root. Everything else is resolved against this. |
| `contentDir` | `string` | `'docs'` | Where markdown pages live. `docsDir` is the legacy alias — setting both throws. |
| `outDir` | `string` | `'dist'` | Production build output. |
| `templatesDir` | `string \| string[]` | `'templates'` | Markdown template files for the frontmatter `template:` field. |
| `layoutsDir` | `string \| string[]` | `'layouts'` | HTML layout files. See [customization →](../guides/customization.html). |
| `layout` | `string` | (unset) | Default HTML layout name applied to every page (frontmatter overrides). |
| `publicDir` | `string \| false` | `'public'` | Static assets copied to the output root. `false` to disable. |
## Site identity
| Field | Type | Default | Purpose |
| --- | --- | --- | --- |
| `title` | `string` | (unset) | Site-wide title. When unset, per-page frontmatter title flows into the header brand AND browser tab. |
| `description` | `string` | (unset) | Site-wide description; falls back as `` when a page omits its own. |
| `siteUrl` | `string` | (unset) | Canonical origin (no trailing slash). Enables ``, `og:url`, sitemap.xml, robots.txt. |
| `themeColor` | `string` | `'#0a1228'` | `` (mobile browser chrome). |
| `ogImage` | `string` | (unset) | Default Open Graph / Twitter image URL (absolute). Per-page `ogImage` frontmatter overrides. |
## Customization
| Field | Type | Default | Purpose |
| --- | --- | --- | --- |
| `css` | `string \| string[]` | (none) | CSS files inlined into every page after `BASE_CSS`. Use for `--mb-*` token overrides. |
| `disableBaseCss` | `boolean` | `false` | Skip Markbook's built-in chrome stylesheet entirely. Pair with `layoutsDir` to own the entire shell. |
| `transformHtml` | `(html, page) => string \| Promise` | (none) | Post-process every page's final HTML. Runs LAST — after layout substitution. Not needed for the common SEO fixes (`index.html`-free canonical / `og:url`, de-duplicated title, de-duplicated ``) — those are automatic. |
## Adapter (component stories)
| Field | Type | Default | Purpose |
| --- | --- | --- | --- |
| `adapter` | `MarkbookAdapter` | `staticAdapter()` | Framework adapter for `:::story` directives. Provided by `@doidor/markbook-adapter-react` (Vue + Web Components adapters are planned). Omit for markdown-only sites. |
The default `staticAdapter` errors clearly if any page tries to use a story directive without an explicit adapter.
## Search and llms.txt
| Field | Type | Default | Purpose |
| --- | --- | --- | --- |
| `search` | `boolean` | `true` | Build the Pagefind full-text index and render the search box. `false` skips Pagefind entirely (no `pagefind/` output, empty `{{ search }}`, no UI script in `{{ bodyEnd }}`). |
| `llmsButtons` | `boolean` | `true` | "View as Markdown" / "Copy as Markdown" buttons above each article. |
Set `search: false` for sites that don't need search, or on platforms where Pagefind's native binary can't run — notably ARM64 Linux with a 16K memory page size (e.g. Raspberry Pi 5), where it aborts with `Unsupported system page size`.
## Dev server
| Field | Type | Default | Purpose |
| --- | --- | --- | --- |
| `dev.port` | `number` | `5173` (Vite default) | Port for `markbook dev`. `markbook preview` uses `port + 1000` by default to avoid clashes. |
| `dev.host` | `string` | (Vite default) | Host to bind. |
## Bundle (`markbook bundle`)
| Field | Type | Default | Purpose |
| --- | --- | --- | --- |
| `bundle.packageScope` | `string` | (unset) | npm scope for `--mode package` outputs (e.g. `'@my-org'`). |
| `bundle.packageVersion` | `string` | `'0.0.1'` | Version written into generated `package.json` files. |
## Playground integration
| Field | Type | Default | Purpose |
| --- | --- | --- | --- |
| `playground` | `PlaygroundConfig \| false` | (none) | "Open in playground" buttons per story (CodeSandbox / StackBlitz). |
| `playground.providers` | `'codesandbox' \| 'stackblitz' \| Array` | required | Which providers to render buttons for. |
| `playground.dependencies` | `Record` | React defaults | npm deps written to the sandbox `package.json`. |
| `playground.stackblitzTemplate` | `string` | `'create-react-app'` | StackBlitz starter template. |
| `playground.inlineSourceImports` | `string[]` | (none) | Globs of in-repo source files to inline into the sandbox so monorepo imports resolve. |
## Full example
```ts
import { defineConfig } from '@doidor/markbook-core';
import { reactAdapter } from '@doidor/markbook-adapter-react/config';
export default defineConfig({
// Layout
contentDir: 'pages',
outDir: 'dist',
publicDir: 'public',
// Identity
title: 'My Component Library',
description: 'A small set of accessible React primitives.',
siteUrl: 'https://my-components.example',
themeColor: '#7c3aed',
ogImage: 'https://my-components.example/og.png',
// Customization
css: ['./brand.css'],
// React stories
adapter: reactAdapter({
decorators: ['./preview.tsx'],
}),
// Playground
playground: {
providers: ['codesandbox', 'stackblitz'],
dependencies: {
react: 'latest',
'react-dom': 'latest',
'@my-org/components': '^1.0.0',
},
inlineSourceImports: ['src/**/*.{ts,tsx}'],
},
// Dev
dev: { port: 5173 },
});
```