kadonix / laravel-swagger-routebook
A tiny route-first Swagger documentation package for Laravel.
Package info
github.com/kadonix/laravel-swagger-routebook
pkg:composer/kadonix/laravel-swagger-routebook
Requires
- php: ^8.1
- illuminate/contracts: ^10.0|^11.0|^12.0
- illuminate/http: ^10.0|^11.0|^12.0
- illuminate/routing: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpunit/phpunit: ^10.0|^11.0
This package is auto-updated.
Last update: 2026-05-07 16:50:10 UTC
README
Laravel Swagger Routebook is a small Laravel package for generating Swagger API documentation from your existing routes and controllers.
It keeps documentation close to your code with simple @ annotations or PHP attributes, then exposes a Swagger UI page, a JSON spec, Postman export, documentation coverage, and a docs audit command.
Features
- Swagger UI at
/docs - JSON spec at
/docs/spec.json - Simple annotations:
@Endpoint,@Parameter,@Body,@Returns,@Response,@Example - PHP attributes for teams that prefer typed metadata
- Route method and path inferred from Laravel routes
- Request schema generation from
FormRequestrules - Response schema generation from DTO classes
components.schemasgeneration for Swagger UI's Schemas section- Bearer auth detection from
auth/auth:*middleware - Grouped specs with
group="admin"and?group=admin - Postman export from the UI or Artisan
- Documentation audit with
routebook:check - Documentation coverage with
routebook:coverage
Requirements
- PHP 8.1+
- Laravel 10, 11, or 12
Installation
composer require kadonix/laravel-swagger-routebook
If you want the constraint to be written with three version digits in your project:
composer require "kadonix/laravel-swagger-routebook:^1.0.0"
Publish the config file if you want to customize Routebook:
php artisan vendor:publish --tag=routebook-config
Then open:
/docs
The generated JSON spec is available at:
/docs/spec.json
Quick Start
Create a normal Laravel controller:
<?php namespace App\Http\Controllers; use App\Http\Requests\StoreProductRequest; use Illuminate\Http\JsonResponse; final class ProductController extends Controller { /** * @Endpoint(summary="List products", tags={"Products"}) * @Parameter(name="search", type="string", description="Filter products by name") * @Parameter(name="page", type="integer", description="Page number") * @Returns(status=200, from="App\Data\ProductData", collection=true, description="Products list") */ public function index(): JsonResponse { return response()->json([ 'data' => [ ['id' => 1, 'name' => 'Keyboard', 'price' => 49.99], ], ]); } /** * @Endpoint(summary="Create product", tags={"Products"}, group="admin") * @Body(from="App\Http\Requests\StoreProductRequest") * @Returns(status=201, from="App\Data\ProductData", description="Product created") * @Response(status=422, description="Validation error") * @Example(name="created", type="response", status=201, value='{"id":1,"name":"Keyboard","price":49.99}') */ public function store(StoreProductRequest $request): JsonResponse { return response()->json([ 'id' => 1, ...$request->validated(), ], 201); } }
Register routes as usual:
use App\Http\Controllers\ProductController; use Illuminate\Support\Facades\Route; Route::get('/products', [ProductController::class, 'index'])->name('products.index'); Route::post('/products', [ProductController::class, 'store'])->name('products.store');
Create a request class:
<?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; final class StoreProductRequest extends FormRequest { public function authorize(): bool { return true; } public function rules(): array { return [ 'name' => 'required|string', 'price' => 'required|numeric', 'stock' => 'required|integer', 'is_active' => 'boolean', ]; } }
Create a response DTO:
<?php namespace App\Data; final class ProductData { public int $id; public string $name; public float $price; }
Routebook will generate schemas for StoreProductRequest and ProductData under components.schemas.
Annotation Reference
@Endpoint
Documents a controller method as an API endpoint.
/** * @Endpoint(summary="List products", tags={"Products"}) */
Full example:
/** * @Endpoint( * method="GET", * path="/products", * summary="List products", * description="Returns paginated products", * tags={"Products"}, * group="public", * auth=true * ) */
Most of the time, you do not need method or path; Routebook infers them from Laravel routes.
Options:
method: HTTP method. Optional.path: API path. Optional.summary: Short title.description: Longer description.tags: Swagger UI tags.group: Used for filtered specs.auth: Forces bearer auth on this endpoint.
@Parameter
Adds query, path, header, or cookie parameters.
/** * @Parameter(name="page", in="query", type="integer", description="Page number") * @Parameter(name="user", in="path", type="integer", required=true) */
Route parameters like /users/{user} are added automatically, so you only need to add extra detail when necessary.
Options:
name: Parameter name.in:query,path,header, orcookie.type: Schema type. Example:string,integer,number,boolean.description: Optional description.required: Boolean.example: Example value.
@Body
Defines the request body.
/** * @Body(from="App\Http\Requests\StoreProductRequest") */
When from points to a Laravel FormRequest, Routebook converts common validation rules into a schema.
Supported rules include:
requiredintegernumericbooleanarraydateemailurl
@RequestBody is also available if you prefer the longer name:
/** * @RequestBody(from="App\Http\Requests\StoreProductRequest") */
@Returns
Defines a successful response.
/** * @Returns(status=200, from="App\Data\ProductData", description="Product found") */
For collections:
/** * @Returns(status=200, from="App\Data\ProductData", collection=true, description="Products list") */
@Response
Defines any response status.
/** * @Response(status=404, description="Product not found") * @Response(status=422, description="Validation error") */
You can also attach a schema:
/** * @Response(status=400, from="App\Data\ErrorData", description="Bad request") */
@Example
Adds examples to request or response content.
/** * @Example(name="payload", type="request", value='{"name":"Keyboard","price":49.99}') * @Example(name="created", type="response", status=201, value='{"id":1,"name":"Keyboard","price":49.99}') */
Options:
name: Example name.type:requestorresponse.status: Response status for response examples.value: JSON or scalar value.
PHP Attributes
If you prefer PHP attributes, Routebook supports them too:
use Kadonix\Routebook\Attributes\Endpoint; use Kadonix\Routebook\Attributes\Parameter; use Kadonix\Routebook\Attributes\Body; use Kadonix\Routebook\Attributes\Returns; use Kadonix\Routebook\Attributes\Response; #[Endpoint(summary: 'List products', tags: ['Products'])] #[Parameter(name: 'page', type: 'integer', description: 'Page number')] #[Returns(status: 200, from: ProductData::class, collection: true, description: 'Products list')] public function index() { // }
DTO Schemas
Routebook reads public properties from DTO classes.
<?php namespace App\Data; use Kadonix\Routebook\Attributes\Schema; #[Schema(required: ['id', 'name'])] final class UserData { #[Schema(description: 'User identifier')] public int $id; public string $name; public ?string $email; }
This appears under Swagger UI's Schemas section.
Authentication
Routebook automatically marks routes protected by auth or auth:* middleware as bearer-authenticated.
Route::middleware('auth:sanctum')->get('/profile', [ProfileController::class, 'show']);
You can also force auth on one endpoint:
/** * @Endpoint(summary="Show profile", tags={"Account"}, auth=true) */
Swagger UI will show its native Authorize button.
To prefill a local development token:
ROUTEBOOK_AUTH_TOKEN=your-local-token
Config:
'auth' => [ 'enabled' => true, 'scheme' => 'bearerAuth', 'type' => 'http', 'scheme_name' => 'bearer', 'bearer_format' => 'JWT', 'token' => env('ROUTEBOOK_AUTH_TOKEN'), ],
Groups
Groups let you generate separate specs for different audiences.
/** * @Endpoint(summary="Create product", tags={"Products"}, group="admin") */
Open a grouped spec:
/docs/spec.json?group=admin
Export a grouped spec:
php artisan routebook:export storage/app/admin.json --group=admin php artisan routebook:export-postman storage/app/admin.postman.json --group=admin
UI Exports
The /docs page includes export links:
SpecPostman
The links respect the current Definition selection.
Artisan Commands
Export the JSON spec:
php artisan routebook:export php artisan routebook:export storage/app/routebook.json
Export a Postman collection:
php artisan routebook:export-postman php artisan routebook:export-postman storage/app/routebook.postman.json
Audit documentation quality:
php artisan routebook:check
This reports:
- undocumented controller routes
- missing summaries
- missing responses
- missing path parameters
Check documentation coverage:
php artisan routebook:coverage
Example output:
Routebook coverage: 100%
3 documented / 3 API routes
Configuration
Published config:
return [ 'title' => env('APP_NAME', 'Laravel API'), 'version' => env('APP_VERSION', '1.0.0'), 'description' => 'Generated with Routebook.', 'routes' => [ 'enabled' => true, 'middleware' => ['web'], 'prefix' => 'docs', 'json' => 'spec.json', ], 'servers' => [ [ 'url' => env('APP_URL', 'http://localhost'), ], ], 'scan' => [ 'include_unannotated_routes' => false, 'default_security' => [], 'detect_auth_middleware' => true, ], 'ui' => [ 'title' => 'API Documentation', 'filter_select' => true, ], ];
By default, Routebook documents only routes with @Endpoint or #[Endpoint].
To include unannotated controller routes:
'scan' => [ 'include_unannotated_routes' => true, ],
Postman Export
From the UI, click Postman.
From the terminal:
php artisan routebook:export-postman storage/app/api.postman.json
The generated collection uses:
{{base_url}}
For protected routes, it adds:
Authorization: Bearer {{token}}
Development Workflow
Recommended checks before publishing:
composer validate --strict vendor/bin/phpunit php artisan routebook:check php artisan routebook:coverage
License
MIT