DocTreen
Adapters

Laravel

PHP adapter — the ->doc() route macro, docs UI, OpenAPI 3.1, validation, drift, and a full CLI.

Package status: early development. Phases 1–4 are in place: docs UI, OpenAPI 3.1 export, 422 validation, drift detection, flows, mock server, codegen, and the CLI. Symfony and PSR-15 adapters are planned.

composer require doctreen/doctreen

Zero runtime dependencies. The service provider is auto-discovered — outside Laravel the class simply never loads.

Mount

There is nothing to mount. Once the package is installed, GET /docs, GET /docs/openapi.json, and the flow-runner endpoint are registered automatically (outside production by default). Configure via config/doctreen.php:

php artisan vendor:publish --tag=doctreen-config

The config keys mirror the shared config object 1:1.

Declare routes with ->doc()

The package registers a doc macro on Laravel routes. Accepted keys mirror defineRoute: description, request (body / query / params), response (single schema or status-keyed map), errors, headers, tags, hidden, security, validate, examples, callbacks.

use Doctreen\Schema\S;

Route::post('/users', [UserController::class, 'store'])->doc([
    'description' => 'Create a user',
    'request'  => ['body' => S::object(['name' => S::string(), 'email' => S::string()])],
    'response' => S::object(['id' => S::number(), 'name' => S::string(), 'email' => S::string()]),
    'errors'   => [409 => 'Email already in use'],
]);

A bare schema is shorthand for the body: 'request' => S::object([...]).

Runtime validation

Enable in config/doctreen.php:

'validate' => ['writeback' => true, 'response' => 'warn'],

Invalid requests get the structured 422 envelope with query and path-param coercion; response is status-aware. The validation middleware attaches to the api and web groups by default — set 'middleware_groups' => [] and register \Doctreen\Laravel\ValidateRequests yourself for full control.

Schema drift detection

'drift' => [
    'enabled' => env('DOCTREEN_DRIFT', env('APP_ENV') !== 'production'),
    'sampleRate' => 0.01,
    'allowReset' => false,
    'resetToken' => null,
],

The default store rides your Laravel cache driver (with a configurable ttl and cacheKey) — so on Redis-backed cache, multi-worker deployments share one aggregated view out of the box. Reports land at GET /docs/drift.json in the shared format.

CLI

The full CLI ships with the package:

# Spec-driven mock server (from a live docs URL or a spec file)
vendor/bin/doctreen mock --from https://api.example.com/docs --port 4000

# Typed PHP codegen: readonly DTOs + a zero-dependency client
vendor/bin/doctreen codegen types  --from openapi.json --out src/Api/Types.php  --namespace 'App\Api'
vendor/bin/doctreen codegen client --from openapi.json --out src/Api/Client.php --namespace 'App\Api'

# Headless flow runner (same flow JSON the docs UI runs)
vendor/bin/doctreen-flow run doctreen-flows/onboarding.json --input email=a@b.c --report json

# Lint your spec, emit it offline, watch for drift in CI
vendor/bin/doctreen lint openapi --url http://localhost:8000/docs --fail-on warning
vendor/bin/doctreen emit-openapi --app . --out openapi.json
vendor/bin/doctreen drift report --url https://api.example.com/docs --fail-on-mismatch

Known limitation

->doc() metadata contains SchemaNode objects, which php artisan route:cache cannot serialise — route caching and doctreen schemas don't mix yet (tracked for a later phase).

On this page