Batteries-included OpenAPI 3.1 generation for Laravel.
Generate an OpenAPI 3.1 document from your existing Laravel routes: no handwritten YAML, no sprawling annotation blocks. Schemas come from the types, PHPDoc, and conventions your code already uses. If something can't be inferred automatically, you can fill the gap with a range of authoring attributes. An included linter reports documentation gaps.
From a typed controller action (plain Laravel, no DTOs or extra packages) openapi:generate produces the full operation
parameters, response, and a reusable component schema:
| Application code | Generated OpenAPI |
|---|---|
/**
* @property string $id
* @property string $number
* @property string $origin
* @property string $destination
* @property Carbon $departs_at
*/
final class Flight extends Model
{
protected $casts = ['departs_at' => 'datetime'];
}
#[Tag('Flights')]
final class FlightController
{
/**
* Show a single flight.
*
* @throws ModelNotFoundException
*/
public function show(string $flight): Flight
{
return Flight::findOrFail($flight);
}
} |
/flights/{flight}:
get:
tags: [ Flights ]
summary: Show a single flight.
operationId: get_flights_flight_
parameters:
- name: flight
in: path
required: true
schema: { type: string }
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Flight'
'404':
$ref: '#/components/responses/NotFound'
components:
schemas:
Flight:
type: object
required: [ id, number, origin, destination, departs_at ]
properties:
id: { type: string }
number: { type: string }
origin: { type: string }
destination: { type: string }
departs_at: { type: string, format: date-time } |
Every part of that document traces back to something already in the code:
- the route
GET /flights/{flight}and its{flight}path parameter come from the route definition and theshow()signature; #[Tag('Flights')]sets the tag, and the first line of the method's PHPDoc becomes the summary;- the
Flightreturn type produces the200response and a reusableFlightschema, whose properties are read from the model's@propertytags, sodeparts_atbecomes adate-timestring because of thedatetimecast; @throws ModelNotFoundExceptionproduces the404.
- OpenAPI 3.1 from your routes: no handwritten YAML, no large annotation blocks.
- Schemas from code you already write: typed return values, request parameters, resource classes, PHPDoc, validation rules, model metadata, and more.
- The rest from convention: tags, path and query parameters, security from auth/scope middleware, error responses
from
@throws. - Authoring attributes for the cases convention can't reach.
- Included Linter (
openapi:lint) that reports documentation gaps and removes redundant annotations. - A served spec and interactive playground (Scalar) out of the box.
- Plugins for Spatie Data, API Resources, Spatie Query Builder, and Fractal.
Requires PHP 8.4+ and Laravel 12 or 13.
composer require radiergummi/laravel-openapiThe service provider is auto-discovered. Generate the document, then check it for gaps:
php artisan vendor:publish --tag=openapi-config # optional
php artisan openapi:generate
php artisan openapi:lintTwo routes are registered by default:
GET /api/openapi.yamlserves the OpenAPI 3.1 document.GET /api/docsserves an interactive Scalar playground (local environment only).
Note
Status: pre-1.0, approaching the first stable release. The generated output is stable in shape; attribute and config names may still change before 1.0.
Most of the spec falls out of your code with zero or minimal configuration:
- Summaries and descriptions from PHPDoc.
- Path parameters from action signatures and route constraints.
- Request bodies from typed
FormRequestor Spatie Data parameters. - Response schemas from return types.
- Security requirements from
auth:*andscope:*middleware. - Error responses from
@throwsand built-in middleware. - Validation constraints from
rules()and validation attributes. operationIdfrom route names, or{method}_{sanitized_path}.- Tags from controller namespaces (or
#[Tag]).
Model schemas are read from @property tags and $casts: Those @property tags are what
laravel-ide-helper generates, so most typed apps already have them.
For anything convention can't derive, the authoring attributes fill the gap.
The richer your types, the richer the spec, and it reads the conventions your stack already uses. Type a controller parameter as a Spatie Data class, and a single definition becomes both the request body and the response schema, validation constraints included:
#[Tag('Flights')]
final class BookingController
{
/** Book a seat on a flight. */
public function store(CreateBookingData $booking): BookingData
{
return BookingData::from(Booking::create($booking->toArray()));
}
}
final class CreateBookingData extends Data
{
public function __construct(
#[Max(200)] public string $passenger_name,
#[Regex('/^\d{1,3}[A-Z]$/')] public string $seat,
) {}
}The CreateBookingData parameter becomes the request body (passenger_name carries maxLength: 200
and seat its pattern) while the BookingData return type becomes the 200 response schema. One class, both
directions.
Core handles FormRequest request bodies directly. Everything else ships as a plugin in config/openapi.plugins:
- SpatieData (default-enabled): request and response schemas from Spatie Data classes, including
DataCollectionandPaginatedDataCollection. No-ops withoutspatie/laravel-datainstalled. - ApiResources (default-enabled):
JsonResource/ResourceCollectionresponses declared with#[ResourceField]. - QueryBuilder (disabled):
filter[…]/sort/includeparameters from#[AllowedFilter]/#[AllowedSort]/#[AllowedInclude]. Requiresspatie/laravel-query-builder. - Fractal (disabled):
league/fractaltransformer responses withDataArray,ArraySerializer, andJsonApienvelopes.
To add your own, implement the Plugin interface. See Plugin authoring.
Eight runnable flavors of a flights/bookings API (vanilla, FormRequest, Spatie Data, API Resources, Fractal, QueryBuilder, swagger-php, and a combined app)
live under examples/ alongside their generated
openapi.yaml snapshots.
openapi:lint generates the spec and reports where it's incomplete: operations without a summary or description,
parameters without descriptions, responses with no declared errors, schemas without examples. Each finding carries a
severity score, from "broken" (a validator would reject the document) down to optional polish.
$ php artisan openapi:lint --level=2
app/Http/Controllers/BookingController.php (2)
│
├─ ⚠️ response.no-error
│ Operation POST /bookings has no error response (4xx/5xx)
│ at app/Http/Controllers/BookingController.php:28 (POST /bookings)
│
│ Suggested Fix: Add at least one error response (e.g. 400, 401, 404, 422, 500) to the operation.
╰─ ℹ️ operation.description-missing
Operation POST /bookings has no description
at app/Http/Controllers/BookingController.php:28 (POST /bookings)
Summary: 1 warning, 1 notice (2 total across 1 route)
Run it in CI (--format=github annotates the pull request), limit it to routes changed since a git ref (--diff), or
restrict it to specific rules. --fix applies the mechanical fixes (removing redundant or no-op annotations, such as a
#[Tag] declared twice), while --check reports without writing, for CI.
php artisan openapi:lint --fix # auto-fix some findings (currently just redundant annotations)
php artisan openapi:lint --check # report only; exits non-zero if a fix is pending (like pint --test)See Linting for the full rule catalog and severity levels.
If you've reached for an OpenAPI tool in Laravel before, here's where this one sits:
- vs. L5-Swagger / handwritten
#[OA\]attributes: those make you write the spec as annotations, a second source of truth you maintain by hand; here it's derived from code you already write. (Importing and migrating off existing#[OA\]/swagger-php annotations is on the roadmap.) - vs. Scribe: Scribe is annotation- and config-driven and renders its own HTML; this leans on your existing types and PHPDoc, emits standard OpenAPI 3.1, and lets you bring your own renderer (Scalar ships wired up).
- vs. Scramble: both packages generate without annotations. Scramble does deeper code analysis: it reads method
bodies (return statements,
validate()calls, resourcetoArray()) and follows the flow, so it often pulls schemas out of code where this package would want a type hint or attribute. The trade-off is determinism: this package stays at types, PHPDoc, attributes, and model metadata (no method-body parsing yet, see Roadmap) and ships a linter that reports exactly where the spec is still thin; Scramble has no equivalent completeness check.
If you're looking for detailed comparisons with specific tools, see the Field report on performance against eleven real-world OSS apps.
A couple of constraints are deliberate and here to stay:
- Static analysis only. It inspects your code, route definitions, types, and metadata; it never executes your controller actions or calls an endpoint to observe a real response. A shape that exists only at runtime (e.g., a payload assembled conditionally, a dynamically keyed array, etc.) can't be read from the source, so describe it with an authoring attribute.
- No bespoke documentation UI. The output is a standard OpenAPI 3.1 document, rendered through the bundled Scalar playground or any OpenAPI tool you prefer. The package won't grow its own HTML documentation renderer.
Gaps that are simply not implemented yet - response schemas from untyped returns, inline validate() bodies - are
tracked on the Roadmap, not here.
The direction below is tracked on the Roadmap project and bucketed into milestones; specifics may shift.
Shipping in 1.0: generation from types, PHPDoc, attributes, and model metadata; Spatie Data and API Resource plugins; multiple specs per app; the linter with CI integration and mechanical auto-fix; the Scalar playground.
Next (v1.1): deeper response- and request-body inference; typed path parameters from route-model binding; query
parameters derived from convention; convention-derived error responses; backed-enum schema components; importing
existing @OA/swagger-php annotations.
Later (v1.2): Tier-1 reading of well-known method-body idioms (inline validate(),
response()->json([...]), abort()); Eloquent API Resource toArray() inference; broader auto-fix (corrections and
stubs) plus migrating off redundant #[OA\] annotations; OpenAPI 3.2.0 output; an interactive "Try it out" docs
playground.
Index: docs/README.md.
- Getting started: install, first spec.
- Auto-derivation: what's derived from what.
- Request bodies:
FormRequestvsData, validation mapping. - Attributes: escape-hatch catalog.
- Recipes: short snippets for specific cases.
- Plugins: bundled plugins.
- Multi-spec: multiple OpenAPI documents per app.
- Linting:
openapi:lint, rule catalog. - Configuration: config keys.
- Troubleshooting: symptom index.
- Field report: how it performed against eleven real-world OSS apps.
- Plugin authoring: write a plugin.
- Architecture: generation pipeline internals.
MIT. See LICENSE.