darkaonline/swagger-lume
SwaggerLume brings Swagger/OpenAPI documentation to Laravel and Lumen apps. It wraps swagger-php to scan your annotations and generate interactive API docs, with a bundled Swagger UI and simple configuration.
SwaggerLume is a thin Lumen wrapper around zircote/swagger-php: it wires annotation scanning, a generate command, and a bundled Swagger UI route into your Lumen app. You own the annotations; the package owns the plumbing. Release 11.0 lines the wrapper up with Lumen 11 and swagger-php 4 — if you're on Lumen 10 or swagger-php 3, stay on the 10.x line.
Minimal path from zero to a rendered spec:
composer require darkaonline/swagger-lume
php artisan swagger-lume:publish # config/swagger-lume.php, views, UI assets
php artisan swagger-lume:generate # writes storage/api-docs/api-docs.json
Register the provider in bootstrap/app.php (or app.php on newer Lumen), and enable facades — the docs controller and UI rendering rely on them:
$app->withFacades();
$app->register(DarkaOnLine\SwaggerLume\SwaggerLumeServiceProvider::class);
// if config() values come back null:
// $app->configure('swagger-lume');
Where to look first:
config/swagger-lume.php — this is the whole integration surface: paths.annotations (what gets scanned, defaults to base_path('app')), paths.docs (output dir), routes.* (the /api/documentation UI and /docs JSON endpoints), scanOptions, constants, generate_always.storage/api-docs/api-docs.json — the artifact you actually ship, diff, and lint. Treat it as a build output with a review step.First use case: annotate exactly one endpoint end-to-end before touching anything else. Put a single required @OA\Info in one file (a dedicated app/OpenApi/Info.php works well), add @OA\Get above one controller method with a @OA\Response, run swagger-lume:generate, and open /api/documentation. Import the annotation namespace at the top of every annotated file — without it, docblock annotations are not resolved:
use OpenApi\Annotations as OA; // required for @OA\* docblock annotations
Getting one operation rendering correctly tells you scanning, the generator, the route, and the assets are all wired. Everything after that is repetition.
Single source of truth for the root document. Keep @OA\Info, @OA\Server, and global @OA\SecurityScheme in one dedicated class (e.g. app/OpenApi/OpenApi.php). swagger-php errors hard if it finds two Info blocks, so centralising the header avoids a class of merge failures.
Environment-aware servers via constants. The config constants block define()s PHP constants at boot; reference them directly in annotations instead of hardcoding hosts:
// config/swagger-lume.php
'constants' => ['L5_SWAGGER_CONST_HOST' => env('L5_SWAGGER_CONST_HOST', 'http://localhost:8000')],
/**
* @OA\Server(url=L5_SWAGGER_CONST_HOST, description="Current environment")
*/
This is the standard way to get one spec that points at staging in CI and localhost in dev without editing annotation files.
Schemas as classes, not inline. Define @OA\Schema on DTOs / resources / models (or a app/OpenApi/Schemas folder added to paths.annotations), then $ref from operations:
/**
* @OA\Schema(schema="User", required={"id","email"})
*/
class UserSchema {
/** @OA\Property(type="integer", example=1) */
public $id;
}
'@OA\JsonContent(ref="#/components/schemas/User")'
Use allOf to compose pagination envelopes (PaginatedUsers = allOf[UserList, Pagination]) rather than repeating fields per endpoint. This is where the day-to-day maintenance cost lives — the more $ref, the less drift.
Attributes over docblocks if your PHP allows it. swagger-php 4 — which 11.0 binds to — reads #[OA\Get(...)] attributes natively. They don't need docblock parsing, survive IDE refactors better, and read as code rather than comments — worth adopting for new endpoints even if legacy controllers stay on annotations. Note the import differs: attributes need use OpenApi\Attributes as OA;.
Dev vs. prod generation. In local development set SWAGGER_GENERATE_ALWAYS=true (or generate_always in config) so the JSON regenerates on each request and you never see stale docs. In production do the opposite: generate at build/deploy time (composer script or deploy hook), commit or artifact-store the JSON, and leave generate_always off — it writes to disk on every request and can leak internal route structure if the docs route is exposed.
Protect the docs route with middleware, not hope. The UI and JSON routes are registered by the provider. Use routes.middleware.docs (and routes.middleware.api) in config to apply Lumen middleware — auth in production, nothing in local. Alternatively disable the routes entirely outside non-prod and serve the static JSON from a docs site.
Validate and diff in CI. The package generates; it does not validate. Add a CI step that runs swagger-lume:generate then lints the output (spectral, swagger-cli validate, or Redocly), and commit api-docs.json so PR diffs show the contract change. An undocumented breaking change becomes visible in review instead of in a client's bug report.
Reuse across the app. For multi-module Lumen apps, point paths.annotations at an array of directories (e.g. app/Http/Controllers, app/OpenApi, app/Modules/*/Http) and use scanOptions.exclude to skip vendor-ish or generated code. Keep one annotation namespace convention so reviewers know where to look.
Required @OA\Info() not found is the first error nearly everyone hits. It means the scanned paths contain no Info block — either you haven't written one, or it lives outside paths.annotations, or you forgot to regenerate.use OpenApi\Annotations as OA; silently erases annotations. Docblock annotations are resolved through the imported namespace; without the use statement the @OA\* tags in that file are skipped with no error, and the operation just doesn't appear. (11.0's docs PR calls this out explicitly — it's the most common "my annotation does nothing" cause.)paths.annotations is silently absent from the spec. No warning, just a missing endpoint. When an operation vanishes, check the path config before debugging the annotation.L5_SWAGGER_CONST_HOST isn't defined (because config didn't load, or you removed the constants entry), the annotation evaluation fails rather than falling back to a literal. Always give the constants entry a default value./api/documentation almost always means the Lumen bootstrap is incomplete: facades not enabled, views/assets not published, or swagger-lume:publish run before the provider was registered. Run publish again after registering and confirm the UI asset files exist.@OA\Schema(schema="User") blocks in different files don't error cleanly — they merge properties. Prefix schemas per module (AdminUser, PublicUser) or keep a single registry.composer.json constraints for zircote/swagger-php before any framework upgrade, and expect to migrate @OA docblocks wholesale if you jump swagger-php majors.proxy and additional_config_url exist for a reason. Behind a load balancer or on a subpath, the UI's fetch of the JSON fails with a CORS/404 mix. Set proxy to the external base URL, or point additional_config_url at a reachable spec.routes.docs vs. routes.api). If the UI loads but shows "fetch failed", the JSON endpoint is blocked by middleware or the wrong prefix, not the assets.generate in a request path in production. Beyond the write cost, generate_always can expose endpoints you deliberately don't document. Gate it to APP_ENV !== 'production'.Generator. If a Lumen release outpaces the wrapper — historically the norm, since 11.0 landed well after Lumen 11 shipped — you can invoke \OpenApi\Generator::scan([...]) directly in a custom artisan command and keep using the published UI assets. The annotations themselves are portable.How can I help you explore Laravel packages today?