Installation
Add the package and publish the config. Install
Hand-written DTOs, validation rules, and controllers drift from the API contract silently. Nobody notices the missing nullable, the renamed wire field, or the new enum case until production rejects a valid payload, or a consumer files the bug for you. The spec already states every one of those shapes; the drift exists only because humans re-type them.
openapi-laravel makes the spec the source of truth and regeneration the sync mechanism. It reads an OpenAPI 3.0 or 3.1 document (3.2 generates on a best-effort path with loud warnings, see supported versions) and, out of the box, generates spatie/laravel-data classes with explicit, spec-derived rules() methods, native PHP enums, one abstract controller per tag, and a routes file, with typed and validated query, path, and header parameters and typed request and response bodies. The output is readable, deterministic PHP that lives in your repo and looks like code you would have written yourself.
The Laravel ecosystem is full of code-first tools that generate an OpenAPI document from your controllers and annotations. This tool goes the other way: the spec is the contract, and your models derive from it.
openapi.yaml → openapi-laravel → app/Data/*.php (laravel-data classes, rules(), enums)(the contract) (the generator) app/Http/Controllers/Api/Abstract* (one abstract controller per tag) routes/api.generated.php (one route per operation)When the spec changes, you re-run the generator and review the diff. There is no second source of truth to keep aligned by hand, and the drift gate (openapi:check) fails CI if anyone forgets.
laravel-data classes with promoted, readonly constructor properties.rules() method derived from the spec: required / nullable, types, string max / min / pattern / format (email, uuid, date, url, ip), numeric min / max, array min / max items, and Rule::enum / Rule::in.#[MapName] when the wire name differs, plus reserved-word and collision handling.#[DataCollectionOf] typed collections.nullable and 3.1 type: [..., 'null'].allOf is merged into one flat class, additionalProperties becomes a typed map (array<string, X>) with per-value rules, and oneOf / anyOf emit native PHP union types for scalars (string|int); a discriminated object union is validated and hydrated via an abstract morphable base, with a deterministic mixed fallback for the messy cases. See the limitations guide for the exact behaviour and residuals.The same run also produces, by default:
routes/api.generated.php): one Route:: entry per operation, pointing at the concrete controller you extend.QueryData / PathData / HeaderData classes enforce a parameter’s spec constraints at runtime (a bad value is a 422, not a silent 200), request bodies (inline JSON, multipart, form-urlencoded, and component $ref) are typed Data params, and object response schemas become typed returns. See the server scaffold guide.Want models only? Opt out with --no-controllers --no-routes or via controllers.enabled / routes.enabled in the config. See the server scaffold guide for the full walkthrough.
Installation
Add the package and publish the config. Install
Quick start
Generate your first Data classes. Quick start
Configuration
Every option in config/openapi-laravel.php. Configuration
Server scaffold
How the default controllers and routes work. Server scaffold
Drift check
Fail CI when generated code and spec disagree. Drift check
End-to-end demo
One spec, a generated Laravel backend and a generated TypeScript client, proven over real HTTP. End-to-end demo
How it compares
Spec-first vs code-first tools. Comparison