tsitsishvili/documentator

Auto-inferred, interactive API documentation for Laravel. Scans your routes, FormRequests, API Resources and return types, lets you override with PHP attributes, and serves an interactive Scalar UI backed by an OpenAPI 3.1 spec.

Maintainers

Package info

github.com/tsitsishvili/documentator

pkg:composer/tsitsishvili/documentator

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.3.0 2026-06-26 06:34 UTC

This package is auto-updated.

Last update: 2026-06-26 06:43:44 UTC


README

Interactive API documentation for Laravel that mostly writes itself.

Documentator scans your application's routes, FormRequests, API Resources and controller return types to infer documentation automatically, lets you refine anything with PHP attributes, and serves an interactive UI — backed by a standard OpenAPI 3.1 document — so third parties can browse your endpoints, read descriptions, and try requests live.

  • Zero-config by default — point it at api/* and you get docs.
  • Attribute overrides — enrich or correct any inferred detail.
  • Built-in explorer — a dark "Aurora" UI (no external assets) with a request playground, auth, snippets, and a readable response viewer. Or switch to Scalar.
  • Standard output — a plain OpenAPI 3.1 document other tools can consume, with response schemas shared by multiple operations hoisted into components/schemas.

Requires PHP 8.2+ and Laravel 12 or 13.

Documentator built-in API explorer showing endpoint navigation, inferred parameters, response schemas, and a request playground

Installation

composer require tsitsishvili/documentator

Publish the config (optional):

php artisan vendor:publish --tag=documentator-config

Visit /docs to see the interactive UI. The raw spec is at /docs/openapi.json.

How inference works

For every route matching config('documentator.routes.match'), an ordered pipeline enriches the endpoint:

Source Produces
Route definition verbs, URI, typed path params (numeric constraint / bound-model key → integer), name, auth guess from auth middleware
Controller PHPDoc summary (first line) and description (the rest), so written docblocks become docs
FormRequest rules() parameters with types, required, enums (in:, Rule::enum, Rule::in), formats (email/uuid/date), bounds (min/max), regexpattern, digits→integer, confirmed→a _confirmation field, nullability, nested rules (items.*.id), file uploads → multipart. On GET/HEAD routes these become query parameters instead of a body
spatie/laravel-data request/response Data objects — typed properties, enums, nested Data, collections (optional, auto-detected)
Controller return type a success response schema from a Resource's toArray(), a ResourceCollection, or a Resource::collection($q->paginate()) return statement (paginator envelope + page/per_page query params), or an Eloquent model ($casts + @property). Status follows the verb: POST → 201, DELETE → 204
Generated examples a representative example for every body/parameter — format- and name-aware (emailuser@example.com, *_url, *_name, dates, enums, …) so the playground starts filled
PHP attributes overrides for everything above (runs last)
/**
 * Create an order.
 *
 * Charges the customer and returns the created order. This summary and
 * description are read straight from the docblock — no attributes needed.
 */
public function store(StoreOrderRequest $request): OrderResource
{
    // Inferred with no attributes: path param {order}, body params from
    // StoreOrderRequest::rules(), a 201 response from OrderResource.
    return new OrderResource(Order::create($request->validated()));
}

With spatie/laravel-data

Install spatie/laravel-data and any Data object you type-hint is documented with no extra annotation — request fields from the argument, response schema from the return type:

public function store(CreateOrderData $data): OrderData
{
    // body params from CreateOrderData's typed properties; 201 response from OrderData
    return OrderData::from(Order::create($data->toArray()));
}

Overriding with attributes

Attributes always win over inference. Mix and match as needed:

use Tsitsishvili\Documentator\Attributes\{Summary, Description, Group, BodyParam, Response, Authenticated};

#[Group('Orders')]
#[Summary('Create an order')]
#[Description('Creates an order for the authenticated customer.')]
#[Authenticated]
#[BodyParam('coupon', 'string', required: false, description: 'Optional promo code')]
#[Response(201, resource: OrderResource::class, description: 'Order created')]
#[Response(422, description: 'Validation failed')]
public function store(StoreOrderRequest $request): OrderResource
{
    // ...
}

Available attributes: Summary, Description, Group, Authenticated, Hidden, Deprecated, BodyParam, QueryParam, PathParam, Response. Group, Authenticated, Hidden and Deprecated may also be placed on the controller class to set a default for all its methods (#[Deprecated] also honours PHP 8.4's native #[\Deprecated]). #[Response(resource: X, paginated: true)] (or collection: true) wraps a resource in the paginator / { data: [...] } envelope; add paginationLinks: false for a custom collection that drops Laravel's links blocks.

Put #[UsesModel(Order::class)] on a Resource to tell the extractor which Eloquent model it wraps (otherwise the model is resolved by naming convention, configurable via models_namespace), so field types come from the model's casts.

Authentication

Auth schemes are declared in config('documentator.security') as OpenAPI securitySchemes. Endpoints behind auth middleware are marked authenticated automatically — and auth:<guard> picks the scheme whose key matches the guard name, falling back to default; use #[Authenticated('scheme-key')] to be explicit or pick a non-default scheme. Token-ability middleware (abilities: / ability: / scopes: / scope:) is surfaced as the operation's required scopes. The UI renders the matching authorize / token input.

To require auth across the whole API instead of marking each endpoint, set documentator.authenticate to true (or a scheme name) — it emits a top-level security requirement applied to every operation. Endpoints that aren't authenticated opt out automatically and stay public.

Trying requests

The built-in explorer can call your API live. It remembers the auth token and selected server across endpoints, deep-links each endpoint (#get-api-orders) for sharing and reload — the Link button copies that deep link — renders Markdown in descriptions, and shows a copyable request snippet in cURL, PHP (Laravel Http), JavaScript (fetch), TypeScript, Python (requests), Go, Ruby, Java, C# and HTTPie — cURL, PHP, JS and TypeScript as tabs with the rest under an Other dropdown, and the chosen language is remembered too. The TypeScript snippet generates typed Request / Response interfaces and an async fetch wrapper (with Date hydration).

The request panel is resizable on desktop and becomes an off-canvas drawer on smaller screens. Use Clear to reset path, query and body inputs without forgetting the selected server or auth token. JSON responses render as a collapsible tree with Expand all / Collapse all, while Copy still copies the full formatted response body. Shortcuts: / focuses search, Cmd/Ctrl+Enter sends, Esc closes panels. Cross-origin "try it" calls require the API to allow CORS from the docs origin.

Production

The docs are open everywhere except production by default; in production set DOCUMENTATOR_ENABLED=true (and/or add auth via route.middleware) to expose them. To restrict who may view the docs, register an authorization gate from a service provider's boot() — it runs after the route middleware, so the authenticated user is available:

use Tsitsishvili\Documentator\Documentator;

Documentator::auth(fn ($request) => $request->user()?->is_admin);

Returning false aborts with a 403. Building the document scans routes per request, so pre-build it:

php artisan documentator:generate                  # warm the cache (set DOCUMENTATOR_CACHE=true)
php artisan documentator:export openapi.json        # write the OpenAPI spec for CI / tooling
php artisan documentator:postman collection.json    # export a Postman v2.1 collection

Keeping docs honest in CI

documentator:check audits the generated docs — it flags closure routes (which can't be introspected) and endpoints with no documented success schema, and can detect drift from a committed spec, listing the specific path / operation / response changes:

php artisan documentator:check                       # report issues (exit 0)
php artisan documentator:check --strict              # fail the build if any issue is found
php artisan documentator:check --against=openapi.json # fail if the spec has drifted; re-export and commit

Configuration

Key options in config/documentator.php:

  • enabled — docs access; null = open except in production, or force true/false. Restrict who may view with Documentator::auth().
  • routes.match / routes.exclude — which routes are documented.
  • route.prefix / route.middleware / route.domain — where the UI is served. Lock it down for private APIs.
  • title / version / description / servers — OpenAPI info and server list.
  • security — auth schemes.
  • authenticate — require a scheme API-wide (true = the default scheme, or a scheme name); false = per-endpoint.
  • error_responses — infer conventional 401/403/404/422 responses from the endpoint shape (default true).
  • infer_status_codes — pick the success status from the verb (POST → 201, DELETE → 204) instead of always 200 (default true).
  • generate_examples — seed an example for every body/parameter that lacks one (default true).
  • models_namespace — where Resources' wrapped models live (for cast-based typing).
  • ui.driverdocumentator (built-in explorer, default) or scalar.
  • ui.assets — Scalar bundle URL when ui.driver = scalar (pinned; self-host for SRI/CSP).
  • cache — pre-generated spec file.
  • extensions.strategies / extensions.openapi_transformers — register custom extraction strategies (resolved from the container, inserted just before attribute overrides) and transformers that receive the generated spec array and may return a modified one. See CONTRIBUTING.md.

Development

composer install
composer test      # Pest + Orchestra Testbench
composer lint      # Laravel Pint

See CONTRIBUTING.md for the architecture tour, how to add an inference strategy or attribute, and the pull-request checklist.