mandu.config.ts Reference
Full schema for ManduConfig — every top-level field and sub-field with types, defaults, and examples.
On this page
mandu.config.ts Reference
The canonical configuration surface for a Mandu project. The schema is the
ManduConfig type exported from @mandujs/core/src/config/mandu.ts. Every
field on this page is defined there; fields not on this page are not part of
the supported config surface at v0.22 / v0.23.
This list reflects v0.22 / v0.23. For later fields, check the
@mandujs/corerelease notes.
File lookup
loadManduConfig(rootDir) probes these paths, in order, and uses the first
that exists:
mandu.config.tsmandu.config.jsmandu.config.json.mandu/guard.json
A .mandu/guard.json whose root object does not contain a guard key is
coerced to { guard: <root> }. A malformed file yields {} — Mandu does
not throw on parse errors; it treats the project as unconfigured.
// mandu.config.ts
import type { ManduConfig } from "@mandujs/core";
export default {
server: { port: 3333 },
guard: { preset: "mandu" },
} satisfies ManduConfig;
Top-level fields
Every top-level field is optional.
| Field | Type | Purpose |
|---|---|---|
adapter |
ManduAdapter |
Runtime adapter override (advanced) |
server |
object | Listener, CORS, streaming, rate-limit |
dev |
object | Dev-server behavior |
build |
object | Production build behavior |
guard |
object | Architecture guard configuration |
fsRoutes |
object | FS Routes discovery rules |
seo |
object | Default SEO metadata |
plugins |
ManduPlugin[] |
Plugin array |
hooks |
Partial<ManduHooks> |
Lifecycle hooks |
adapter
Type: ManduAdapter (from @mandujs/core).
Swap out the runtime adapter. Most projects leave this unset and use Mandu's default Bun adapter.
server
server?: {
port?: number;
hostname?: string;
cors?: boolean | {
origin?: string | string[];
methods?: string[];
credentials?: boolean;
};
streaming?: boolean;
rateLimit?: boolean | {
windowMs?: number;
max?: number;
message?: string;
statusCode?: number;
headers?: boolean;
};
};
| Field | Type | Notes |
|---|---|---|
port |
number |
Overridden by PORT env and --port CLI flag |
hostname |
string |
Listener hostname |
cors |
boolean | object |
true for permissive defaults, object for fine control |
cors.origin |
string | string[] |
Allowed origin(s) |
cors.methods |
string[] |
Allowed methods |
cors.credentials |
boolean |
Allow credentials |
streaming |
boolean |
Enable streaming responses |
rateLimit |
boolean | object |
true for default limit, object for fine control |
rateLimit.windowMs |
number |
Window length in ms |
rateLimit.max |
number |
Max requests per window |
rateLimit.message |
string |
Response body on limit |
rateLimit.statusCode |
number |
Response status on limit |
rateLimit.headers |
boolean |
Emit rate-limit headers |
Example:
server: {
port: 3333,
hostname: "0.0.0.0",
cors: { origin: ["https://example.com"], credentials: true },
rateLimit: { windowMs: 60_000, max: 120, headers: true },
}
dev
dev?: {
hmr?: boolean;
watchDirs?: string[];
/** Observability SQLite persistent store (default: true) */
observability?: boolean;
};
| Field | Type | Default | Notes |
|---|---|---|---|
hmr |
boolean |
true (runtime-default) |
Hot module replacement |
watchDirs |
string[] |
— | Extra directories to watch |
observability |
boolean |
true |
Enable the observability SQLite store |
Example:
dev: { hmr: true, watchDirs: ["content", "packages/shared"] }
build
build?: {
outDir?: string;
minify?: boolean;
sourcemap?: boolean;
splitting?: boolean;
};
| Field | Type | Notes |
|---|---|---|
outDir |
string |
Output directory (default follows .mandu/client conventions) |
minify |
boolean |
Minify output |
sourcemap |
boolean |
Emit sourcemaps |
splitting |
boolean |
Code-splitting |
Example:
build: { outDir: "dist", minify: true, sourcemap: true, splitting: true }
guard
guard?: {
preset?: "mandu" | "fsd" | "clean" | "hexagonal" | "atomic" | "cqrs";
srcDir?: string;
exclude?: string[];
realtime?: boolean;
rules?: Record<string, GuardRuleSeverity>;
contractRequired?: GuardRuleSeverity;
};
| Field | Type | Notes |
|---|---|---|
preset |
enum | "mandu" | "fsd" | "clean" | "hexagonal" | "atomic" | "cqrs" |
srcDir |
string |
Source root to scan |
exclude |
string[] |
Path globs to skip |
realtime |
boolean |
Enable realtime Guard watcher |
rules |
Record<string, GuardRuleSeverity> |
Per-rule severity override |
contractRequired |
GuardRuleSeverity |
Severity when a route lacks a contract |
GuardRuleSeverity = "error" | "warn" | "warning" | "off".
Example:
guard: {
preset: "fsd",
srcDir: "src",
exclude: ["**/*.test.ts"],
realtime: true,
rules: { "no-cross-feature-import": "error" },
contractRequired: "warn",
}
A guard-only file is also valid:
// .mandu/guard.json
{
"preset": "mandu",
"rules": { "no-cross-feature-import": "error" }
}
The loader will wrap this into { guard: ... } automatically.
fsRoutes
fsRoutes?: {
routesDir?: string;
extensions?: string[];
exclude?: string[];
islandSuffix?: string;
};
| Field | Type | Notes |
|---|---|---|
routesDir |
string |
Routes root (defaults to app/) |
extensions |
string[] |
File extensions considered routable |
exclude |
string[] |
Path globs to skip |
islandSuffix |
string |
Interactive-component suffix (conventionally .island.tsx) |
Example:
fsRoutes: {
routesDir: "app",
extensions: [".tsx", ".ts"],
islandSuffix: ".island.tsx",
}
seo
seo?: {
enabled?: boolean;
defaultTitle?: string;
titleTemplate?: string;
};
| Field | Type | Notes |
|---|---|---|
enabled |
boolean |
Turn SEO defaults on/off |
defaultTitle |
string |
Fallback <title> |
titleTemplate |
string |
Template applied to per-route titles |
Example:
seo: {
enabled: true,
defaultTitle: "Mandu",
titleTemplate: "%s — Mandu",
}
plugins
Type: ManduPlugin[] (from @mandujs/core).
plugins: [myPlugin(), anotherPlugin({ verbose: true })]
hooks
Type: Partial<ManduHooks> (from @mandujs/core).
hooks: {
// populate the hook handlers you need; others are left to Mandu defaults
}
Full example
// mandu.config.ts
import type { ManduConfig } from "@mandujs/core";
export default {
server: {
port: 3333,
cors: { origin: ["https://example.com"], credentials: true },
streaming: true,
rateLimit: { windowMs: 60_000, max: 120, headers: true },
},
dev: { hmr: true, observability: true, watchDirs: ["content"] },
build: { outDir: "dist", minify: true, sourcemap: true, splitting: true },
guard: {
preset: "mandu",
srcDir: "src",
exclude: ["**/*.test.ts"],
realtime: true,
rules: { "no-cross-feature-import": "error" },
contractRequired: "warn",
},
fsRoutes: {
routesDir: "app",
extensions: [".tsx", ".ts"],
islandSuffix: ".island.tsx",
},
seo: { enabled: true, defaultTitle: "Mandu", titleTemplate: "%s — Mandu" },
plugins: [],
hooks: {},
} satisfies ManduConfig;
🤖 Agent Prompt
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/reference/config to my project.
Summary of the page:
ManduConfig schema from @mandujs/core/src/config/mandu.ts. Use this to know which fields are allowed in mandu.config.ts, what types they take, and which files Mandu will load. Do not invent fields not listed here.
Required invariants — must hold after your changes:
- All top-level fields on ManduConfig are optional — a config file may be empty
- Mandu searches, in order: mandu.config.ts, mandu.config.js, mandu.config.json, .mandu/guard.json
- .mandu/guard.json may contain either the full config or a guard-only object; in the guard-only case it is coerced to { guard: ... }
- When a config file exists but fails to load or parse, loadManduConfig returns {} — invalid configs do not throw
- guard.preset must be one of: mandu, fsd, clean, hexagonal, atomic, cqrs
- GuardRuleSeverity is limited to: error, warn, warning, off
Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.
For Agents
ManduConfig schema from @mandujs/core/src/config/mandu.ts. Use this to know which fields are allowed in mandu.config.ts, what types they take, and which files Mandu will load. Do not invent fields not listed here.
- All top-level fields on ManduConfig are optional — a config file may be empty
- Mandu searches, in order: mandu.config.ts, mandu.config.js, mandu.config.json, .mandu/guard.json
- .mandu/guard.json may contain either the full config or a guard-only object; in the guard-only case it is coerced to { guard: ... }
- When a config file exists but fails to load or parse, loadManduConfig returns {} — invalid configs do not throw
- guard.preset must be one of: mandu, fsd, clean, hexagonal, atomic, cqrs
- GuardRuleSeverity is limited to: error, warn, warning, off