Installation:
composer require vyuldashev/laravel-openapi
Publish the config file:
php artisan vendor:publish --provider="Vyuldashev\LaravelOpenApi\OpenApiServiceProvider"
Basic Configuration:
Edit config/openapi.php to define your API's metadata (title, version, description).
Example:
'info' => [
'title' => 'My API',
'version' => '1.0.0',
'description' => 'API Documentation',
],
First Use Case: Annotate a controller method to generate OpenAPI docs:
use Vyuldashev\LaravelOpenApi\Annotations\OpenApi;
class UserController extends Controller
{
/**
* @OpenApi(
* tags={"Users"},
* summary="Get a user by ID",
* @OA\Parameter(
* name="id",
* description="User ID",
* required=true,
* in="path",
* @OA\Schema(type="integer")
* ),
* @OA\Response(
* response=200,
* description="User data",
* @OA\JsonContent(ref="#/components/schemas/User")
* )
* )
*/
public function show($id)
{
return User::findOrFail($id);
}
}
Generate Documentation: Run the generator:
php artisan openapi:generate
Output will be saved to storage/openapi.json.
Tagging Routes:
Use @OpenApi(tags={"TagName"}) to group endpoints in Swagger UI.
/**
* @OpenApi(tags={"Auth"})
*/
public function login()
{
// ...
}
Request/Response Schemas:
Define reusable schemas in config/openapi.php under components.schemas:
'components' => [
'schemas' => [
'User' => [
'type' => 'object',
'properties' => [
'id' => ['type' => 'integer'],
'name' => ['type' => 'string'],
],
],
],
],
Reference them in annotations:
@OA\Response(
response=200,
description="User data",
@OA\JsonContent(ref="#/components/schemas/User")
)
Authentication:
Add security schemes to config/openapi.php:
'securitySchemes' => [
'bearerAuth' => [
'type' => 'http',
'scheme' => 'bearer',
'bearerFormat' => 'JWT',
],
],
Apply globally or per endpoint:
@OpenApi(
security={{"bearerAuth": []}},
// ...
)
Conditional Annotations: Use PHP logic to conditionally apply annotations:
/**
* @OpenApi(
* @OA\Response(
* response=200,
* description="Success",
* @OA\JsonContent(
* properties={
* "data" => ["type" => "string"],
* "status" => ["type" => "string", "default" => "success"]
* }
* )
* )
* )
*/
public function dynamicResponse()
{
if ($this->isAdmin()) {
// Override response schema for admins
}
}
Middleware Integration: Use middleware to inject dynamic data (e.g., user roles) into annotations:
class OpenApiContextMiddleware
{
public function handle($request, Closure $next)
{
$request->openapiContext = ['userRole' => auth()->user()->role];
return $next($request);
}
}
Access in annotations:
@OA\Response(
response=200,
@OA\JsonContent(
properties={
"permissions" => ["type" => "array", "items" => ["type" => "string"]],
// Dynamically set based on userRole
}
)
)
Route::prefix('v1')->group(function () {
Route::get('/users', [UserController::class, 'index']);
});
Route::prefix('v2')->group(function () {
Route::get('/users', [UserController::class, 'index']);
});
Override config per version:
'versions' => [
'v1' => [
'info' => ['version' => '1.0.0'],
'paths' => ['/v1/users'],
],
'v2' => [
'info' => ['version' => '2.0.0'],
'paths' => ['/v2/users'],
],
],
Caching Issues: Clear cached annotations after changes:
php artisan openapi:clear
Or disable caching in config/openapi.php:
'cache' => false,
Schema Conflicts:
Avoid naming conflicts in components.schemas. Use namespaces:
'components' => [
'schemas' => [
'User_v1' => [...],
'User_v2' => [...],
],
],
Annotation Parsing: Ensure annotations are on the method, not the class, for per-endpoint docs. Incorrect:
/**
* @OpenApi(...) // This applies to the whole class!
*/
class UserController extends Controller { ... }
Circular References:
Avoid circular references in schemas (e.g., User referencing Profile, which references User). Use $ref sparingly.
Validate JSON Output:
Use JSONLint to validate storage/openapi.json.
Common errors:
Check Annotations:
Enable debug mode in config/openapi.php:
'debug' => true,
This logs parsed annotations to storage/logs/laravel-openapi.log.
Partial Generation: Generate docs for a specific controller:
php artisan openapi:generate --controller=UserController
Custom Annotations: Extend the parser by creating a custom annotation class:
namespace App\OpenApi\Annotations;
use Vyuldashev\LaravelOpenApi\Annotations\OpenApiAnnotation;
class CustomAnnotation extends OpenApiAnnotation
{
public function parse($content)
{
// Custom logic
}
}
Register in OpenApiServiceProvider:
$this->app->bind(\Vyuldashev\LaravelOpenApi\Annotations\AnnotationParser::class, function () {
return new \App\OpenApi\Annotations\CustomParser();
});
Post-Processing: Hook into the generation lifecycle via events:
// In EventServiceProvider
protected $listen = [
\Vyuldashev\LaravelOpenApi\Events\OpenApiGenerated::class => [
\App\Listeners\PostProcessOpenApi::class,
],
];
Example listener:
public function handle(OpenApiGenerated $event)
{
$event->spec->info->title = 'Processed: ' . $event->spec->info->title;
}
Swagger UI Customization: Override the default Swagger UI template by publishing and modifying:
php artisan vendor:publish --tag=openapi-views
Edit resources/views/vendor/openapi/swagger.blade.php.
Exclude Routes:
Skip generating docs for non-API routes in config/openapi.php:
'ignore_routes' => [
'admin/*',
'healthcheck',
],
Selective Annotation Parsing:
Use @ignore to exclude specific methods:
/**
* @ignore
*/
public function adminOnly()
{
// No OpenAPI docs
}
Batch Generation: For large APIs, generate docs incrementally:
php artisan openapi:generate --chunk=50
Processes 50 routes at a time.
How can I help you explore Laravel packages today?