Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

L5 Swagger Laravel Package

darkaonline/l5-swagger

L5-Swagger packages swagger-php and Swagger UI for Laravel, letting you generate and serve OpenAPI/Swagger documentation for your API with minimal setup and a simple config file.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and publish (5 minutes).

composer require darkaonline/l5-swagger
php artisan vendor:publish --provider "L5Swagger\L5SwaggerServiceProvider"

You now have config/l5-swagger.php and a docs folder at storage/api-docs. The published README is a link-farm to the Wiki — read Installation & Configuration first, then config/l5-swagger.php top to bottom; that file is the documentation for every knob (routes, paths, scan options, security schemes, UI flags).

2. Look at the config before writing any docblocks. The three keys that decide your life:

  • documentations.default.paths.docs → storage_path('api-docs') (generated JSON/YAML lives here)
  • documentations.default.paths.annotations / scanOptions.paths → what gets scanned (default base_path('app'))
  • documentations.default.routes.api → api/documentation (the Swagger UI URL)

3. Define the root Info once. The spec is only valid if swagger-php finds an OA\Info somewhere inside a scanned path. Dump it on your base controller (or a dedicated App\OpenApi\OpenApi class) — this is the very first thing to get right, because everything else fails until it exists:

namespace App\Http\Controllers;

use OpenApi\Attributes as OA;

#[OA\Info(version: '1.0.0', title: 'My API', description: 'Internal API')]
#[OA\Server(url: '/api', description: 'Default')]
#[OA\SecurityScheme(
    securityScheme: 'bearerAuth',
    type: 'http',
    scheme: 'bearer',
    bearerFormat: 'JWT'
)]
abstract class Controller
{
    //
}

4. Annotate one endpoint and generate.

#[OA\Get(
    path: '/users/{id}',
    summary: 'Show a user',
    security: [['bearerAuth' => []]],
    tags: ['Users'],
    parameters: [new OA\Parameter(name: 'id', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))],
    responses: [
        new OA\Response(response: 200, description: 'OK',
            content: new OA\JsonContent(ref: '#/components/schemas/User')),
        new OA\Response(response: 404, description: 'Not found'),
    ]
)]
public function show(User $user) { /* ... */ }
php artisan l5-swagger:generate
open http://myapp.test/api/documentation

That loop — annotate, l5-swagger:generate, refresh UI — is the whole day-to-day workflow. First real use case: get the spec in front of frontend/mobile consumers so they stop asking you what the response shape is, then feed the same JSON into Postman/Insomnia/client generators.

Implementation Patterns

Attributes live on the controller, not in a parallel doc file. The idiomatic Laravel pattern is one OA\* attribute per action, OA\Schema attributes on API Resources / DTOs, and OA\Parameter for path/query params. Group with tags: ['Users'] and describe the error envelope once as a schema.

Reuse schemas via ref + ::class, not copy-paste. swagger-php resolves ref: UserResource::class when the class carries #[OA\Schema]; attribute arguments must be constant expressions, so use class constants for enums/shared strings:

#[OA\Schema(schema: 'User')]
class UserResource extends JsonResource { /* ... */ }

Domain-split large specs. Put the shared info/security in one class (App\OpenApi\OpenApi), per-domain path attributes in per-module classes, and either keep scanOptions.paths pointed at app_path() or narrow it to [app_path('Http/Controllers/Api'), app_path('OpenApi')] to keep generation fast. For totally separate audiences (public API vs. admin API), use multiple documentations entries in the config, each with its own paths and routes.api, then php artisan l5-swagger:generate <documentation> to build just that one.

Auth: configure security + securityDefinitions once. Sanctum/Passport both reduce to a bearerAuth scheme; add a global security entry in the config (or per-operation) and enable persist_authorization in the UI so testers don't re-paste tokens after every refresh. oauth2 with PKCE is supported via the UI config keys.

Generate in deploy, not on request. Set generate_always => false (production default) and add the generate step to your deploy script / CI job right after composer install:

php artisan l5-swagger:generate
# spec now at storage/api-docs/api-docs.json

Treat the spec as an artifact under test. In CI, generate and then lint the output with swagger-php's own validator (vendor/bin/openapi --validate) or assert the JSON parses and contains expected paths. This catches the "spec silently generated without Info" and "one controller's attributes threw and were skipped" classes of breakage before your consumers do.

Customize the UI via config, then views. Most UI behaviour (doc_expansion, filter, deepLinking, try_it_out, dark mode, format_to_use_for_docs = json|yaml) is env-driven through L5_SWAGGER_UI_* keys. When that's not enough, publish the package views (vendor:publish --tag=...) and override the template — it's a plain Blade file.

Protect or gate the docs route. Wrap routes.middleware with auth/can: or a basic-auth middleware, or set the api_key config so /api/documentation requires ?api_key=. Ship the spec internally and keep it out of the public surface.

Gotchas and Tips

  • Annotations vs. attributes is a hard cliff. doctrine/annotations was dropped in 10.0.0, so the legacy /** @OA\Get(...) */ docblock style no longer parses. Any tutorial or StackOverflow answer with @OA\ in a comment is for v8/v9. Use #[OA\...] attributes.
  • "Required @OA\Info() not found" / empty spec almost always means: (a) no OA\Info in a scanned path, (b) scanOptions.paths points somewhere your file isn't, or (c) your annotations sit on a file with no class (orphan annotations are silently ignored). Move the attribute onto a class.
  • Attributes must be constant expressions. No function calls, no config() inside attributes — env(), now(), and string concatenation will blow up at generation time. Use class constants and L5_SWAGGER_CONST_HOST for the host.
  • Config caching bites. After php artisan config:cache, changes to L5_SWAGGER_* env values won't apply until you re-cache. Conversely, if env vars appear ignored, check you're not cached.
  • generate_always = true in production regenerates and re-scans on every page load — slow, and a needless attack surface. It's the #1 cause of "why is /api/documentation taking 8 seconds".
  • Host/scheme wrong behind a proxy or in Docker. The UI derives the base URL from the request; set OA\Server explicitly (or the constants config) rather than trusting X-Forwarded-*. Mixed-content "try it out" failures are usually this.
  • 404 on the docs route after adding php artisan route:cache — the package routes are registered but the cache may be stale; re-run route:clear && route:cache. Also confirm the route didn't collide with a catch-all {any} route.
  • Storage permissions. storage/api-docs must be writable by the web user if anything generates at runtime; in CI you can just read the generated file. Commit or .gitignore it deliberately — don't let it drift between environments.
  • Path-traversal / UI asset issues are version-sensitive. Fixes landed in 8.6.4 (path traversal) and 8.6.3 (L5_SWAGGER_UI_ASSETS_PATH); stay on a current 10.x/11.x line rather than pinning an old 8.x. v9+ needs PHP ≥ 8.2, Laravel ≥ 11, swagger-php ≥ 5; v10.1 moves to swagger-php 6, which is a breaking change for any custom processors.
  • Debugging tactic: read storage/api-docs/api-docs.json directly and diff it against expectations — it's the ground truth, not the UI. If the JSON looks right but the UI doesn't, it's a view/config/asset problem; if the JSON is wrong, it's scanning/attributes. Running vendor/bin/openapi app -o /tmp/spec.json standalone isolates whether swagger-php or L5-Swagger is at fault.
  • Extension points: custom OpenApi\Processors via the config (v11.1 also allows positioning them relative to built-in processors), a custom generator_factory, and overriding the controller/view. Use these to inject global tags, sort operations, or add vendor extensions like x-code-samples without touching every controller.
  • Enums and validation drift. The spec doesn't read your FormRequest rules, so validation and docs diverge silently. Either derive parameter descriptions from the rule array or add a CI test that every form request has a matching documented body — otherwise the "docs" become a lie within two sprints.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core