Configuration
Madori is configured via a single TypeScript file at your project root: madori.config.ts. This file controls content paths, Control Panel settings, GraphQL behaviour, and authentication. All options have sensible defaults — you only need to configure what you want to change.
Configuration Reference
Full Config Schema
import type { MadoriConfigInput } from './src/lib/config/schema'
const config: MadoriConfigInput = {
contentPath: './content',
resourcesPath: './resources',
usersPath: './users',
assetsPath: './public/assets',
git: {
enabled: false,
automatic: true,
push: false,
trackedPaths: [
{ root: 'content', exclude: ['forms/**'] },
{ root: 'resources' },
],
},
cp: {
enabled: true,
path: '/cp',
},
graphql: {
enabled: true,
path: '/api/graphql',
introspection: process.env.NODE_ENV !== 'production',
},
staticCache: {
enabled: false,
driver: 'application',
storagePath: './storage/static-cache',
exclude: [],
queryStrings: 'ignore',
warmOnInvalidate: false,
},
sites: [
{ handle: 'default', url: 'https://www.example.com', locale: 'en-US', default: true },
],
seo: {
enabled: true,
metadata: true,
structuredData: true,
sitemap: true,
robots: true,
humans: true,
reports: true,
redirects: true,
errorTracking: false,
socialImages: false,
allowExternalCanonicals: false,
allowedRedirectOrigins: [],
trailingSlash: 'never',
reportRetentionDays: 90,
reportSnapshotLimit: 50,
operationalStoragePath: './storage/seo',
},
auth: {
driver: 'password',
store: 'file',
provider: 'yaml',
storeConfig: {
sessionsDir: './.sessions',
sessionDurationMs: 86400000,
},
},
}
export default config
Path Options
| Option | Type | Default | Description |
|---|---|---|---|
contentPath |
string |
./content |
Directory where content entries, globals, forms, and navigation are stored |
resourcesPath |
string |
./resources |
Directory where blueprints, fieldsets, roles, and definitions live |
usersPath |
string |
./users |
Directory where user YAML files are stored |
assetsPath |
string |
./public/assets |
Directory where uploaded assets are stored |
Git Content Sync
Git sync is disabled by default. When enabled, Madori commits successful content changes to each repository containing a configured tracked root. Pushing to a remote is separately opt-in.
| Option | Type | Default | Description |
|---|---|---|---|
git.enabled |
boolean |
false |
Enable content Git synchronization |
git.automatic |
boolean |
true |
Queue commits after content changes |
git.push |
boolean |
false |
Push successful commits to configured remote |
git.debounceMs |
number |
2000 |
Coalesce rapid changes before committing |
git.trackedPaths |
{ root: string, exclude?: string[] }[] |
content and resources |
Built-in roots (content, resources, assets, users) or explicit paths; assets and users are opt-in |
git.remote |
string |
origin |
Existing Git remote name |
git.branch |
string |
unset | Optional branch to push |
git.commitPrefix |
string |
[Madori] |
Prefix for generated commit messages |
git.statePath |
string |
./storage/git-sync |
Durable pending-sync state |
Madori never stores Git credentials. Configure authentication in Git itself (for example, an SSH deploy key or credential helper). Tracked paths may point into separate repositories, but only explicitly configured paths are staged. users and assets remain excluded unless added to trackedPaths; consider privacy and large-file storage before enabling them.
For setup, GitHub authentication, separate repositories, recovery, and troubleshooting, see Git Content Sync.
Control Panel Options
| Option | Type | Default | Description |
|---|---|---|---|
cp.enabled |
boolean |
true |
Enable or disable the Control Panel entirely |
cp.path |
string |
/cp |
URL path prefix for the Control Panel |
GraphQL Options
| Option | Type | Default | Description |
|---|---|---|---|
graphql.enabled |
boolean |
true |
Enable or disable the GraphQL API |
graphql.path |
string |
/api/graphql |
URL path for the GraphQL endpoint |
graphql.introspection |
boolean |
true in dev, false in prod |
Allow schema introspection queries |
When introspection is disabled, __schema and __type fields are rejected during GraphQL validation; ordinary fields, including __typename, remain available. GraphiQL is unavailable, but normal GET and POST queries still work subject to authentication and permissions.
The bundled Next.js routes and Control Panel links use /cp and /api/graphql. Changing cp.path or graphql.path alone does not relocate those routes; keep the defaults unless you also adapt application routing, links, and route protection.
Static HTML Cache
| Option | Type | Default | Description |
|---|---|---|---|
staticCache.enabled |
boolean |
false |
Enable bounded caching for eligible anonymous HTML responses |
staticCache.driver |
application | file |
application |
Cache storage implementation |
staticCache.storagePath |
string |
storage/static-cache/ |
Cache storage location |
staticCache.exclude |
string[] |
[] |
Glob patterns excluded from caching |
staticCache.queryStrings |
ignore | separate |
ignore |
Query-string cache-key policy |
staticCache.warmOnInvalidate |
boolean |
false |
Warm invalidated URLs when configured |
staticCache.invalidationRules |
array |
[] |
Content trigger to URL/glob mappings |
Cache applies only to eligible public HTML on the configured site origin. Requests with cookies, authorization, RSC/prefetch variants, or private/no-store/no-cache responses pass through. Use one writable application process per storage location; the cache coordination is not a distributed lock across independent deployments.
Sites and SEO Options
sites defines public site contexts used by URL resolution, metadata, alternate links, sitemaps, and redirects. Exactly one site must be marked default; each URL must be an HTTP(S) origin without credentials, query parameters, or fragments. Use separate handles for domain-based sites or locales served from a shared host.
| Option | Type | Default | Description |
|---|---|---|---|
sites[].handle |
string |
default |
Stable site identifier used in SEO documents and API requests |
sites[].url |
string |
http://localhost:3000 |
Public origin for canonical and alternate URLs |
sites[].locale |
string |
en-US |
Locale emitted in alternate metadata |
sites[].default |
boolean |
false |
Select exactly one fallback site |
seo.enabled |
boolean |
true |
Master SEO switch; disables SEO output and endpoints |
seo.metadata |
boolean |
true |
Emit title, description, canonical, robots, and social metadata |
seo.structuredData |
boolean |
true |
Emit validated JSON-LD |
seo.sitemap |
boolean |
true |
Enable /sitemap.xml generation |
seo.robots |
boolean |
true |
Enable /robots.txt generation |
seo.humans |
boolean |
true |
Enable /humans.txt generation |
seo.reports |
boolean |
true |
Enable SEO audit report APIs and snapshots |
seo.redirects |
boolean |
true |
Enable authored redirect management and runtime redirects |
seo.errorTracking |
boolean |
true |
Record bounded, normalized public 404 observations |
seo.socialImages |
boolean |
false |
Emit resolved social-image overrides |
seo.allowExternalCanonicals |
boolean |
false |
Permit explicitly authored external canonical URLs |
seo.allowedRedirectOrigins |
string[] |
[] |
Exact external origins permitted as redirect destinations; redirects stay local by default |
seo.trailingSlash |
always | never | preserve |
never |
Canonical path normalization policy |
seo.reportRetentionDays |
number |
90 |
Retention window for operational report snapshots |
seo.reportSnapshotLimit |
number |
50 |
Maximum retained report snapshots |
seo.operationalStoragePath |
string |
./storage/seo |
Runtime SEO storage; keep outside content Git paths |
SEO defaults are authored in versioned files under resources/seo/; redirects are versioned under content/seo/redirects/. 404 observations are persisted in not-found-observations.json and audit snapshots under the configured operational storage path; do not commit that operational directory to content Git. Runtime caches and counters are process/runtime concerns rather than a documented persistent storage contract. See SEO Architecture for the storage contract.
Authentication Options
| Option | Type | Default | Description |
|---|---|---|---|
auth.driver |
string |
password |
Authentication driver — validates credentials |
auth.store |
string |
file |
Session storage backend |
auth.provider |
string |
yaml |
User data provider |
auth.storeConfig.sessionsDir |
string |
./.sessions |
Directory for session files |
auth.storeConfig.sessionDurationMs |
number |
86400000 (24h) |
Session expiry duration in milliseconds |
Directory Structure
my-site/
├── content/
│ ├── collections/ # Entry files (Markdown + YAML frontmatter)
│ ├── globals/ # Global data (YAML)
│ ├── forms/ # Form submissions (YAML)
│ ├── navigation/ # Navigation trees (YAML)
│ └── taxonomies/ # Taxonomy terms (YAML)
├── resources/
│ ├── blueprints/ # Field schemas
│ │ ├── collections/
│ │ ├── globals/
│ │ ├── taxonomies/
│ │ └── forms/
│ ├── collections/ # Collection definitions
│ ├── definitions/ # Navigation definitions
│ ├── fieldsets/ # Reusable field groups
│ ├── roles/ # Permission roles
│ └── taxonomies/ # Taxonomy definitions
├── users/ # User accounts (YAML)
├── public/assets/ # Uploaded files
└── madori.config.ts # Project configuration
Usage Examples
Minimal Configuration
The simplest valid config uses all defaults:
import type { MadoriConfigInput } from './src/lib/config/schema'
const config: MadoriConfigInput = {}
export default config
This gives you a fully functional CMS with the Control Panel at /cp and GraphQL at /api/graphql.
Custom Paths
Change where content and resources are stored:
const config: MadoriConfigInput = {
contentPath: './data/content',
resourcesPath: './data/resources',
assetsPath: './public/media',
}
export default config
Disable GraphQL in Production
Keep GraphQL available in development but disable it in production:
const config: MadoriConfigInput = {
graphql: {
enabled: process.env.NODE_ENV !== 'production',
path: '/api/graphql',
introspection: false,
},
}
export default config
Custom Control Panel Path
Mount the Control Panel at a non-default URL:
const config: MadoriConfigInput = {
cp: {
enabled: true,
path: '/admin',
},
}
export default config
The Control Panel is now accessible at http://localhost:3000/admin.
Extended Session Duration
Keep editors logged in for 7 days instead of the default 24 hours:
const config: MadoriConfigInput = {
auth: {
driver: 'password',
store: 'file',
provider: 'yaml',
storeConfig: {
sessionsDir: './.sessions',
sessionDurationMs: 7 * 24 * 60 * 60 * 1000, // 7 days
},
},
}
export default config
Common Patterns
Environment-Specific Configuration
Use environment variables to adjust configuration per environment:
const config: MadoriConfigInput = {
graphql: {
enabled: true,
path: '/api/graphql',
introspection: process.env.NODE_ENV !== 'production',
},
cp: {
enabled: process.env.DISABLE_CP !== 'true',
path: '/cp',
},
}
export default config
Headless Mode (API Only)
Disable the Control Panel entirely for a headless setup where content is managed via files or external tools:
const config: MadoriConfigInput = {
cp: {
enabled: false,
},
graphql: {
enabled: true,
path: '/api/graphql',
},
}
export default config
Monorepo Setup
In a monorepo where content lives separately from the application:
const config: MadoriConfigInput = {
contentPath: '../../packages/content/data',
resourcesPath: '../../packages/content/resources',
usersPath: '../../packages/content/users',
assetsPath: './public/assets',
}
export default config
Secure Production Defaults
A production-hardened configuration:
const config: MadoriConfigInput = {
graphql: {
enabled: true,
path: '/api/graphql',
introspection: false,
},
auth: {
driver: 'password',
store: 'file',
provider: 'yaml',
storeConfig: {
sessionsDir: './.sessions',
sessionDurationMs: 4 * 60 * 60 * 1000, // 4 hours
},
},
}
export default config
Managing Settings in the Control Panel
The Control Panel includes a Settings page at /cp/settings. Runtime settings (site_name, locale, and timezone) are stored separately from application configuration. The configuration editor writes supported fields to madori.config.ts, including paths, sites, SEO, Git, and cache options. Changes are validated before writing; configuration changes require a rebuild and process restart in production. Authored content and SEO documents have separate runtime write paths.
Git-Ignored Sessions Directory
Add the sessions directory to .gitignore to avoid committing session data:
.sessions/
This is included by default in Madori's generated .gitignore.