Configuration
All adapters accept the same config object — in every language.
Every adapter — in Node.js, Python, and PHP — accepts the same config shape with the same keys and the same defaults. Knowledge transfers 1:1:
expressAdapter(app, {
docsPath: '/docs', // URL where the docs UI is served
enabled: true, // Set false to disable; defaults to NODE_ENV !== 'production'
liveReload: false, // Re-discover routes on every docs hit
meta: {
title: 'My API',
version: '1.0.0',
description: 'Full description shown in the UI header',
},
exclude: ['/health', /^\/internal\/.*/], // Paths to hide from docs
groups: { // Group routes under named sections in the sidebar
Users: ['/users', '/users/:id'],
Products: '/products',
},
flows: [/* ... */], // Inline flow presets (see Request Flows)
flowsPath: './doctreen-flows', // Directory of *.json flow files
});flask_adapter(app, {
"docsPath": "/docs",
"enabled": True, # defaults to "not production" env detection
"meta": {
"title": "My API",
"version": "1.0.0",
"description": "Full description shown in the UI header",
},
"exclude": ["/health"],
"groups": {"Users": ["/users", "/users/:id"]},
"validate": {"writeback": True, "response": "warn"},
"drift": {"enabled": True, "sampleRate": 0.01},
})Keys are deliberately camelCase — identical to the Node config — so examples, docs, and team knowledge apply unchanged across the stack.
<?php
// config/doctreen.php — publish with:
// php artisan vendor:publish --tag=doctreen-config
return [
'docsPath' => '/docs',
'enabled' => env('DOCTREEN_ENABLED', env('APP_ENV') !== 'production'),
'meta' => [
'title' => env('APP_NAME', 'API Documentation'),
'version' => '1.0.0',
'description' => '',
],
'exclude' => [], // exact paths, or '#regex#'
'groups' => [],
'validate' => false, // true | ['writeback' => true, 'response' => 'warn']
'defaultErrors' => [],
'drift' => [
'enabled' => env('DOCTREEN_DRIFT', env('APP_ENV') !== 'production'),
'sampleRate' => 0.01,
],
'flows' => [],
'flowsPath' => null, // default: ./doctreen-flows under the project root
];Reference
| Option | Type | Default | Description |
|---|---|---|---|
docsPath | string | '/docs' | URL where the docs UI is served |
enabled | boolean | NODE_ENV !== 'production' (APP_ENV on Laravel) | Set to false to disable docs entirely |
liveReload | boolean | false | Re-discover routes on every docs request (Node) |
meta.title | string | 'API Documentation' | Title shown in the UI header |
meta.version | string | '1.0.0' | Version label |
meta.description | string | '' | Description shown below the title |
exclude | string | RegExp | Array | [] | Routes to hide from the docs |
groups | Record<string, string | string[]> | {} | Group routes into named sidebar sections |
flows | FlowDefinition[] | null | Inline request-flow presets |
flowsPath | string | auto-detected | Directory of *.json flow files |
validate | boolean | ValidateConfig | false | Run runtime validation on every schema-declared route |
defaultErrors | Record<number, string | { description?, schema? }> | {} | Default error responses merged into every route (v1.15+) |
drift | boolean | DriftConfig | env-dependent | Enable schema drift detection |
openapi | OpenAPIConfig | {} | Tags, servers, security schemes, callbacks/webhooks for the OpenAPI export |
headHtml | string | '' | Inject custom <head> HTML (analytics, favicons, OG tags) |
A few language-specific notes: Laravel adds middleware_groups (which route
middleware groups the validation middleware attaches to — default
['api', 'web']); Python's drift config accepts an announce_routes store
hook; and the FastAPI adapter defaults docsPath to /doctreen instead of
/docs, so it never collides with FastAPI's own Swagger UI. Everything else
is shared.
Disabling in production
By default enabled is false in production (NODE_ENV === 'production' on
Node, non-production APP_ENV detection on Laravel, environment detection on
Flask). To serve docs in production, set it explicitly:
expressAdapter(app, { enabled: true, meta: { title: 'My API', version: '1.0.0' } });Excluding routes
exclude accepts strings, regexes, or arrays of either (PHP uses
'#regex#' strings for patterns):
expressAdapter(app, {
exclude: [
'/health',
'/metrics',
/^\/internal\/.*/,
],
});For per-route control while keeping the route reachable, use
hidden: true on
defineRoute / @DocRoute / @define_route / ->doc() instead.
Validation options v1.15
validate accepts a boolean or an object for finer control:
validate: boolean | {
enabled?: boolean; // Run validation (default when object form is used)
writeback?: boolean; // Write the parsed payload back onto the request
response?: 'off' | 'warn' | 'throw'; // Dev-mode response assertion (default 'off')
}validate: trueis shorthand for{ enabled: true }— validate the request, but don't mutate it (legacy behavior).writeback: truewrites the parsed (coerced and defaulted) payload back onto the request, so your handler sees the cleaned values.response: 'warn' | 'throw'asserts handler responses against their declared schema in development.
The same object shape works in Python ({"validate": {"writeback": True}}) and
PHP ('validate' => ['writeback' => true]). See
runtime validation for the full behavior
per language.
Default error responses v1.15
defaultErrors declares error responses applied to every route, keyed by HTTP
status — the same shape as a route's errors. It
kills the boilerplate of repeating 401 / 403 / 422 on every
defineRoute. Each route's own errors win on a status conflict.
expressAdapter(app, {
defaultErrors: {
401: 'Authentication required',
403: 'Forbidden',
422: { description: 'Validation failed', schema: s.object({ error: s.string() }) },
},
});On Laravel, declare the same map in config/doctreen.php — schemas built with
\Doctreen\Schema\S work here exactly as they do on routes.