DocTreen

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

OptionTypeDefaultDescription
docsPathstring'/docs'URL where the docs UI is served
enabledbooleanNODE_ENV !== 'production' (APP_ENV on Laravel)Set to false to disable docs entirely
liveReloadbooleanfalseRe-discover routes on every docs request (Node)
meta.titlestring'API Documentation'Title shown in the UI header
meta.versionstring'1.0.0'Version label
meta.descriptionstring''Description shown below the title
excludestring | RegExp | Array[]Routes to hide from the docs
groupsRecord<string, string | string[]>{}Group routes into named sidebar sections
flowsFlowDefinition[]nullInline request-flow presets
flowsPathstringauto-detectedDirectory of *.json flow files
validateboolean | ValidateConfigfalseRun runtime validation on every schema-declared route
defaultErrorsRecord<number, string | { description?, schema? }>{}Default error responses merged into every route (v1.15+)
driftboolean | DriftConfigenv-dependentEnable schema drift detection
openapiOpenAPIConfig{}Tags, servers, security schemes, callbacks/webhooks for the OpenAPI export
headHtmlstring''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: true is shorthand for { enabled: true } — validate the request, but don't mutate it (legacy behavior).
  • writeback: true writes 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.

On this page