mtrajano/laravel-swagger
Generate Swagger/OpenAPI documentation for Laravel APIs directly from your routes and controller annotations. mtrajano/laravel-swagger scans your app to produce interactive API docs, reducing manual spec maintenance and keeping endpoints in sync.
Install as a dev dependency so generation stays out of production builds:
composer require --dev mtrajano/laravel-swagger
php artisan vendor:publish --provider="Mtrajano\LaravelSwagger\LaravelSwaggerServiceProvider"
Minimal config/laravel-swagger.php:
return [
'output' => 'storage/api-docs/api-docs.json',
'format' => 'json',
'routes' => [
'prefix' => 'api',
'middleware' => [], // optional narrowing
],
'docs' => [
'info' => ['title' => 'My API', 'version' => '1.0.0'],
'servers' => [['url' => config('app.url') . '/api']],
],
];
Generate and inspect:
php artisan laravel-swagger:generate
cat storage/api-docs/api-docs.json | jq '.paths | keys'
Where to look first: the generated file, not the package source. Diff it against reality to see what the scanner picked up. Then open config/laravel-swagger.php — almost everything (route scope, global info, security) is driven from there.
First use case: pick a single controller — typically a read-only GET /api/... index or show — add a @response docblock to the action, regenerate, and import the JSON into Swagger Editor or Postman. Once one endpoint round-trips cleanly, expand controller by controller.
Convention-driven generation. The package walks routes matching the configured prefix/middleware, resolves controller actions, and derives path parameters, request bodies (from FormRequest rules), and responses (from docblocks). Your leverage comes from keeping code consistent so the conventions hold.
Document via controller docblocks. Put a summary/description and typed response examples above each action:
/**
* List users
*
* Returns a paginated list of users.
*
* @response 200 {
* "data": [{"id": 1, "name": "Jane"}],
* "meta": {"current_page": 1}
* }
*/
public function index() { ... }
For large payloads prefer @responseFile 200 tests/Responses/users_index.json — inline JSON is fragile and bloats controllers.
Lean on FormRequest for bodies. A StoreUserRequest with scalar rules (required|string|max:255, email, integer, boolean, array, in:...) maps cleanly to a request schema. Keep the rules() method declarative; push custom logic into withValidator/after so it doesn't confuse inference.
Tag by controller. Add a class-level or first-method @tags/summary so endpoints group sensibly in the UI. Consistent tags make the spec usable as an onboarding doc rather than a dump.
Scope with route filters per audience. For a public/partner/internal split, invoke generation multiple times with different --routes / --except combinations and distinct --output files rather than trying to express everything in one config.
Auth schemes in config, referenced in code. Define securityDefinitions (Bearer, ApiKey) in the docs block, then annotate controllers that require it. This is the only clean way to get the "Authorize" button working in Swagger UI.
CI workflow. Regenerate on every PR that touches routes/controllers, validate with swagger-cli validate storage/api-docs/api-docs.json, and either commit the artifact or publish it as a build artifact consumed by your docs site. A failing validation is a cheap guard against broken annotations landing on main.
Preview locally. Point any Swagger UI instance (Docker swaggerapi/swagger-ui, the hosted editor, or a tiny Blade page) at the generated file. Don't rely on editing the JSON by hand — regenerate.
dedoc/scramble/knuckleswtf/scribe for greenfield work.prefix/middleware/filter simply won't appear. When an endpoint is missing, debug the filter config first — not the docblock. Verbose output (-v) on the artisan command usually reveals the scanned route list.@response JSON is brittle. A trailing comma, unescaped quote, or multiline array breaks parsing or produces garbage. Use @responseFile for anything beyond trivial payloads, and validate the generated JSON in CI.Rule objects, conditional arrays (required_if, exclude_if), regex rules, and file uploads are frequently under- or mis-described. Treat inferred schemas as a starting point and hand-annotate where accuracy matters (uploads, unions, polymorphic bodies).request()->validate() inside controllers. Declare them explicitly or move the validation into a FormRequest and verify the generated parameter location./users/{user} without a typed parameter often lands as a string; add a typed signature or an explicit annotation to get integer.--append is stateful. Appending to an existing spec keeps stale paths around after route renames/deletes. Prefer a clean regenerate unless you have a specific multi-source merge reason.JsonResource collections, pagination wrappers, and transformers are invisible to the scanner. Document those shapes explicitly or your consumers will build against the wrong contract.info, servers, securityDefinitions, and route filters are supported; per-endpoint customisation happens through docblock annotations. If you need more, patch/fork rather than trying to coerce config.How can I help you explore Laravel packages today?