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/doctreenZero 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-configThe 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-mismatchKnown 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).