dedoc/scramble
Scramble generates OpenAPI 3.1.0 documentation for your Laravel API automatically from your code, with no PHPDoc annotations required. Just install it to get a docs UI at /docs/api and a JSON spec at /docs/api.json, always kept up to date with your API.
composer require dedoc/scramble
That is the entire installation. There is no service provider to register, no config to publish before first use, and no annotations to write. Install it, boot the app, and two routes appear:
/docs/api — a rendered UI (Stoplight Elements) against your live spec/docs/api.json — the raw OpenAPI 3.1.0 documentBoth are registered only in the local environment by default. That single default is the first thing to internalise: if a colleague says "the docs route 404s on staging", nothing is broken — you need to define the viewApiDocs gate (documented at scramble.dedoc.co/usage/getting-started#docs-authorization) to widen access.
Where to look first
GET /docs/api.json — read the spec, not the UI. The UI is a thin renderer; every real question ("why is this field a string?") is answered in the JSON.php artisan vendor:publish to pull config/scramble.php. This is where ui.title, theme (system is supported), flatten_deep_query_parameters, and the form-request extractor ignore list live.scramble.dedoc.co.First use case: pick an existing resource controller with a FormRequest and an ApiResource response. Open /docs/api and you should see the endpoint grouped, the request body built from your rules() array (with required, email, max:255, exists:... mapped to types and constraints), and the response shaped from the resource's toArray(). That round trip — install, look, compare against your code — tells you immediately how much inference you get for free and where you will need to intervene.
Treat validation rules as the request schema. Scramble reads rules() and messages(); it understands Rule::requiredIf, Rule::when, Rule::enum, regex, date_format, wildcard keys (items.*.sku), and exists/unique against model columns (including UUID columns). The day-to-day workflow is therefore: write correct rules, get correct docs. If a parameter is not validated (a filter, a sort key, a ?include=), document it with the #[QueryParameter] attribute on the controller method rather than inventing a fake rule.
Treat API Resources as the response contract. Recent versions infer resources and resource collections deeply, including paginationInformation() and paginator transformation. This is the highest-leverage pattern: return UserResource::collection($users) or $users->paginate(15) and the schema, the data wrapper, and the pagination envelope fall out automatically. #[ResponseFromApiResource] lets you pin a response for endpoints whose return type is not statically readable.
Lean on return-type inference, especially on Laravel 11/12+. The 0.13 line added full type inference, closure-route support, and Collection<int, Model> resolution from Model::query()->get(). Flow-sensitive analysis and paginate inference mean an expressive controller body is often enough. Where inference stops short, a precise return type or a @var/@return PHPDoc is cheaper than a manual response annotation.
Group and describe deliberately. #[Group] organises the sidebar; #[Endpoint(operationId: ..., title: ..., description: ...)] gives you stable operation IDs — which matters the moment you generate a client SDK, because operation IDs are the method names. Set them intentionally before clients ship.
Automate the spec in CI. Since /docs/api.json is a plain endpoint, curl it in a pipeline and commit or diff the result. That gives you (a) a reviewable diff whenever an endpoint's shape changes — an accidental breaking change shows up in the PR — and (b) a stable artifact for feeding OpenAPI generators. Use Scramble::extendOpenApi() and Scramble::configure() for security schemes, servers, and anything the inference layer cannot know (auth flows, tenancy headers, rate-limit metadata).
Split environments by gate, not by branch. Define viewApiDocs to allow local plus an internal-IP or authenticated-admin condition. Keeping the spec reachable in staging is worth a lot during QA; keeping it public is not.
response()->json($someComputedArray()), DB::select(...), and arrays assembled through dynamic keys degrade to object/array with no properties. Add a @return/@var shape or an explicit #[Response]. This is the single most common source of "why is my response empty".@hidden and @deprecated are your steering wheel. @hidden (annotation) and the attribute let you drop internal endpoints or fields; @deprecated on fields and resource properties flows into the schema. Prefer these over deleting endpoints from the app.$hidden/$visible and casts affect the schema. A datetime:Y-m-d cast now correctly documents as date rather than date-time — but custom casts and accessors are where inference weakens. Verify those fields in /docs/api.json rather than trusting the UI.__toString() contributes nothing; a Rule::when or requiredIf chain is handled, but a hand-rolled closure rule generally is not. Document the affected parameter explicitly.Rule::requiredIf was once over-reported as required, and enum handling shifted when Laravel 12.48 made Enum stringable. If a field's requiredness looks wrong after a Laravel upgrade, check the Scramble changelog before rewriting your rules.flatten_deep_query_parameters changes whether filter[status] appears as a nested object or flattened names. Also ensure body extension respects the same setting — a past release specifically fixed a mismatch there.composer.json bump. Pin ^0.13 and treat Scramble upgrades as spec changes: diff api.json in CI and review./docs/api.json, find the operation, and inspect components.schemas. If the schema is wrong, the fix is in code (a type, a rule, an annotation, an extension) — not in the UI layer.#[QueryParameter], #[Response], #[Endpoint]) → Scramble::extendOpenApi() → a custom extension class registered via config. Resist reaching for extensions before inference has been given clean code to read; the whole value of the package is that the docs stay true to the source.How can I help you explore Laravel packages today?