Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Add Docs
Scramble
https://github.com/dedoc/scramble
Admin
Scramble generates API documentation for Laravel projects in OpenAPI 3.1.0 format without requiring
...
Tokens:
10,979
Snippets:
68
Trust Score:
5.9
Update:
1 week ago
Context
Skills
Chat
Benchmark
78.4
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Scramble – Laravel API Documentation Generator Scramble (`dedoc/scramble`) is a Laravel package that automatically generates OpenAPI 3.1.0 documentation for Laravel applications **without requiring manual PHPDoc annotations**. It statically analyses controller methods, FormRequest classes, Eloquent models, and response types to produce accurate, always-up-to-date API documentation. Documentation is served live via two auto-registered routes: a Stoplight Elements UI viewer at `/docs/api` and a raw OpenAPI JSON spec at `/docs/api.json`. The package works by parsing PHP ASTs and running a type-inference engine (`Infer`) over controller code, resolving return types from `response()->json(...)`, `JsonResource`, Eloquent collections, paginators, and exception handlers. Developers can enrich the output using PHP 8 attributes (`#[Response]`, `#[QueryParameter]`, `#[Group]`, etc.) or PHPDoc inline annotations (`@body`, `@return`). The extension system (`TypeToSchemaExtension`, `OperationExtension`, `ExceptionToResponseExtension`) allows deep customisation without forking the core. --- ## Installation Install via Composer and optionally publish the config file. ```bash composer require dedoc/scramble # Optionally publish config php artisan vendor:publish --provider="Dedoc\Scramble\ScrambleServiceProvider" --tag="scramble-config" ``` After installation two routes are available: - `GET /docs/api` — Stoplight Elements UI - `GET /docs/api.json` — Raw OpenAPI 3.1.0 JSON --- ## Configuration — `config/scramble.php` Central configuration file controlling path matching, UI options, servers, enum serialisation, and middleware. ```php // config/scramble.php return [ // Only routes whose URI starts with this prefix are included 'api_path' => 'api', // Restrict to a specific domain (null = app domain) 'api_domain' => null, // File written by `php artisan scramble:export` 'export_path' => 'api.json', 'info' => [ 'version' => env('API_VERSION', '0.0.1'), 'description' => 'My awesome API', ], 'ui' => [ 'title' => null, // defaults to app name 'theme' => 'light', // 'light' | 'dark' | 'system' 'hide_try_it' => false, 'hide_schemas' => false, 'logo' => '', 'try_it_credentials_policy' => 'include', 'layout' => 'responsive', // 'sidebar' | 'responsive' | 'stacked' ], // Override server list (null = auto-generate from api_path / api_domain) 'servers' => null, // 'description' | 'extension' | false 'enum_cases_description_strategy' => 'description', // 'names' | 'varnames' | false 'enum_cases_names_strategy' => false, 'flatten_deep_query_parameters' => true, 'middleware' => [ 'web', \Dedoc\Scramble\Http\Middleware\RestrictedDocsAccess::class, ], // Extra TypeToSchemaExtension / OperationExtension / ExceptionToResponseExtension classes 'extensions' => [], ]; ``` --- ## `Scramble::configure()` — Fluent API Configuration Returns the `GeneratorConfig` instance for the default (or a named) API, enabling programmatic configuration in a service provider. ```php use Dedoc\Scramble\Scramble; use Dedoc\Scramble\Support\RouteInfo; use Dedoc\Scramble\Support\Generator\Operation; use Illuminate\Routing\Route; // In AppServiceProvider::boot() // 1. Restrict which routes appear in the docs Scramble::routes(fn (Route $route) => str_starts_with($route->uri, 'api/v1')); // 2. Post-process the whole OpenAPI document Scramble::afterOpenApiGenerated(function (array $openApi) { $openApi['info']['x-logo'] = ['url' => 'https://example.com/logo.png']; return $openApi; }); // 3. Custom tag logic from PHPDoc @tags annotation Scramble::resolveTagsUsing(function (RouteInfo $routeInfo, Operation $operation): array { $tags = $routeInfo->phpDoc()->getTagsByName('@tags'); return array_map(fn ($tag) => trim($tag->value->value), $tags); }); // 4. Prevent unknown schema types from appearing (throws in production) Scramble::preventSchema(\Dedoc\Scramble\Support\Generator\Types\UnknownType::class); // 5. Custom rule enforcement Scramble::enforceSchema( cb: fn ($schema, string $path) => ! ($schema instanceof \Dedoc\Scramble\Support\Generator\Types\UnknownType), errorMessageGetter: 'Unknown type at path: $0', ignorePaths: ['/paths/api/legacy'], throw: false, // log only, don't abort ); ``` --- ## `Scramble::registerApi()` — Multiple API Documentation Instances Registers a named secondary API documentation set, each with its own config, route filter, and UI/document endpoints. ```php use Dedoc\Scramble\Scramble; use Illuminate\Routing\Route; // In AppServiceProvider::boot() // Disable the default docs routes Scramble::ignoreDefaultRoutes(); // Register a versioned public API Scramble::registerApi('v2', ['api_path' => 'api/v2']) ->expose(ui: 'docs/v2', document: 'docs/v2/openapi.json') ->routes(fn (Route $r) => str_starts_with($r->uri, 'api/v2')); // Register a separate internal API Scramble::registerApi('internal', ['api_path' => 'internal']) ->expose(ui: 'docs/internal', document: 'docs/internal.json') ->routes(fn (Route $r) => str_starts_with($r->uri, 'internal')); // Customise per-API config fluently Scramble::configure('v2') ->withDocumentTransformers(function (\Dedoc\Scramble\Configuration\DocumentTransformers $dt) { $dt->append(AddSecuritySchemeTransformer::class); }) ->preferPatchMethod(); // force PATCH over PUT|PATCH pairs ``` --- ## `GeneratorConfig::routes()` — Custom Route Filtering Supplies a closure that determines whether a given route is included in the generated documentation. ```php use Dedoc\Scramble\Scramble; use Illuminate\Routing\Route; // Include only routes tagged with a specific middleware Scramble::configure()->routes(function (Route $route): bool { return in_array('api', $route->middleware()) && ! str_starts_with($route->uri, 'api/internal'); }); // Include every route (useful when api_path is '') Scramble::routes(fn () => true); ``` --- ## `#[Endpoint]` Attribute — Override Endpoint Metadata Attaches an explicit `operationId`, summary title, description, or HTTP method override to a controller method. ```php use Dedoc\Scramble\Attributes\Endpoint; class UserController extends Controller { #[Endpoint( operationId: 'users.list', title: 'List Users', description: 'Returns a paginated list of all users in the system.', )] public function index(): JsonResponse { return response()->json(User::paginate()); } // Force documentation as PATCH even though the route accepts PUT|PATCH #[Endpoint(method: 'patch')] public function update(Request $request, User $user): UserResource { $user->update($request->validated()); return new UserResource($user); } } ``` --- ## `#[Group]` Attribute — Grouping and Ordering Endpoints Groups controller routes under a named tag and controls sort order of groups in the documentation sidebar. ```php use Dedoc\Scramble\Attributes\Group; // Class-level group — all routes in this controller get tag "Orders" #[Group(name: 'Orders', description: 'Endpoints for managing customer orders', weight: 0)] class OrderController extends Controller { public function index() { /* ... */ } public function store() { /* ... */ } } // Method-level group with ordering class ReportController extends Controller { #[Group(name: 'Reports', weight: 1)] public function daily() { /* ... */ } #[Group(name: 'Reports', weight: 2)] public function monthly() { /* ... */ } } // Generated OpenAPI tags section: // "tags": [{"name": "Orders", "description": "Endpoints for managing customer orders"}, {"name": "Reports"}] ``` --- ## `#[QueryParameter]`, `#[BodyParameter]`, `#[PathParameter]`, `#[HeaderParameter]` Attributes — Parameter Documentation Annotates or overrides individual request parameters with type, description, default value, and examples. ```php use Dedoc\Scramble\Attributes\QueryParameter; use Dedoc\Scramble\Attributes\BodyParameter; use Dedoc\Scramble\Attributes\PathParameter; use Dedoc\Scramble\Attributes\Example; class ProductController extends Controller { #[QueryParameter('per_page', description: 'Items per page', type: 'int', default: 15)] #[QueryParameter('sort', description: 'Sort field', type: 'string', example: 'created_at')] #[QueryParameter('direction', description: 'Sort direction', type: 'string', examples: [ 'asc' => new Example('asc', summary: 'Ascending'), 'desc' => new Example('desc', summary: 'Descending'), ] )] public function index(Request $request): JsonResponse { return response()->json(Product::paginate($request->per_page ?? 15)); } #[PathParameter('product', description: 'The product ID', type: 'int')] #[BodyParameter('name', description: 'Product name', required: true)] #[BodyParameter('price', description: 'Price in cents', type: 'int', example: 1999)] public function update(Request $request, int $product): ProductResource { $p = Product::findOrFail($product); $p->update($request->validate(['name' => 'required|string', 'price' => 'required|int'])); return new ProductResource($p); } } ``` --- ## `#[Response]` Attribute — Manual Response Declaration Manually declares or augments response types, status codes, media types, and descriptions on a controller method. ```php use Dedoc\Scramble\Attributes\Response; class InvoiceController extends Controller { // Simple: status + description + inline type string #[Response(200, description: 'Invoice details', type: 'array{id: int, total: float, paid: bool}')] public function show(Invoice $invoice): JsonResponse { return response()->json($invoice); } // Multiple media types on the same status #[Response(200, description: 'JSON response when download=false')] #[Response(200, mediaType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', type: 'string', format: 'binary')] public function export(bool $download = false): mixed { /** @body array{id: int, total: float} */ return $download ? response()->download('invoice.xlsx') : response()->json($this->data); } // Add a 201 response alongside the inferred 200 #[Response(201, type: 'array{id: int, status: string}', description: 'Invoice created')] public function store(Request $request): JsonResponse { $invoice = Invoice::create($request->validated()); return response()->json($invoice, 201); } } ``` --- ## `#[Header]` Attribute — Response Header Documentation Documents response headers (e.g., rate-limit, pagination cursors) for a specific status code. ```php use Dedoc\Scramble\Attributes\Header; class ApiController extends Controller { #[Header('X-RateLimit-Limit', type: 'int', description: 'Max requests per minute', status: 200)] #[Header('X-RateLimit-Remaining', type: 'int', description: 'Remaining requests', status: 200)] #[Header('X-Cursor', type: 'string', description: 'Pagination cursor', status: 200)] public function index(): JsonResponse { return response()->json(Item::paginate()) ->header('X-RateLimit-Limit', 60) ->header('X-RateLimit-Remaining', 59); } } ``` --- ## `#[ExcludeRouteFromDocs]` and `#[ExcludeAllRoutesFromDocs]` Attributes — Hiding Endpoints Prevents specific methods or entire controllers from appearing in the generated documentation. ```php use Dedoc\Scramble\Attributes\ExcludeRouteFromDocs; use Dedoc\Scramble\Attributes\ExcludeAllRoutesFromDocs; class UserController extends Controller { public function index(): JsonResponse // included { return response()->json(User::all()); } #[ExcludeRouteFromDocs] public function internalSync(): void // excluded from docs { // internal sync job endpoint } } // All routes in this controller are hidden from docs #[ExcludeAllRoutesFromDocs] class HealthCheckController extends Controller { public function ping() { return 'pong'; } public function status() { return response()->json(['ok' => true]); } } ``` --- ## `#[SchemaName]` and `#[Hidden]` Attributes — Schema Customisation `#[SchemaName]` renames a class-based schema in the OpenAPI components section; `#[Hidden]` omits a property from the schema. ```php use Dedoc\Scramble\Attributes\SchemaName; use Dedoc\Scramble\Attributes\Hidden; // Rename the schema used in OpenAPI components #[SchemaName(name: 'UserProfile', input: 'UserProfileInput')] class UserResource extends JsonResource { public function toArray($request): array { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'token' => $this->api_token, // will appear as 'token' in schema ]; } } // In a DTO / data class class CreateUserData { public function __construct( public string $name, public string $email, #[Hidden] public string $internalNote = '', // omitted from OpenAPI schema ) {} } ``` --- ## `OperationExtension` — Custom Operation Transformer Extends every matching operation in the generated OpenAPI document (e.g., inject a security scheme, add common headers). ```php use Dedoc\Scramble\Extensions\OperationExtension; use Dedoc\Scramble\Support\Generator\Operation; use Dedoc\Scramble\Support\RouteInfo; class BearerAuthOperationExtension extends OperationExtension { public function handle(Operation $operation, RouteInfo $routeInfo): void { // Apply only to routes that use the 'auth:sanctum' middleware if (! in_array('auth:sanctum', $routeInfo->route->middleware())) { return; } $operation->addSecurityRequirement(['bearerAuth' => []]); } } // Register in config/scramble.php // 'extensions' => [BearerAuthOperationExtension::class], // Or programmatically: // Scramble::registerExtension(BearerAuthOperationExtension::class); ``` --- ## `TypeToSchemaExtension` — Custom Type-to-Schema Mapping Maps a custom PHP type to an OpenAPI schema or response, enabling first-class support for non-standard response classes. ```php use Dedoc\Scramble\Extensions\TypeToSchemaExtension; use Dedoc\Scramble\Support\Generator\Response; use Dedoc\Scramble\Support\Generator\Schema; use Dedoc\Scramble\Support\Generator\Types\ObjectType; use Dedoc\Scramble\Support\Type\ObjectType as PhpObjectType; use Dedoc\Scramble\Support\Type\Type; class ApiResponseTypeToSchema extends TypeToSchemaExtension { /** Return true if this extension handles the given type */ public function shouldHandle(Type $type): bool { return $type instanceof PhpObjectType && $type->isInstanceOf(\App\Http\Responses\ApiResponse::class); } /** Map the type to an OpenAPI Response */ public function toResponse(Type $type): ?Response { return Response::make(200) ->setDescription('Standard API envelope') ->setContent('application/json', Schema::fromType( (new ObjectType) ->addProperty('success', new \Dedoc\Scramble\Support\Generator\Types\BooleanType) ->addProperty('data', new \Dedoc\Scramble\Support\Generator\Types\ObjectType) ->setRequired(['success', 'data']) )); } } // Register in config/scramble.php 'extensions' or via Scramble::registerExtension(...) ``` --- ## `ExceptionToResponseExtension` — Custom Exception-to-Response Mapping Documents error responses that arise from specific exception types thrown inside controller methods. ```php use Dedoc\Scramble\Extensions\ExceptionToResponseExtension; use Dedoc\Scramble\Support\Generator\Reference; use Dedoc\Scramble\Support\Generator\Response; use Dedoc\Scramble\Support\Generator\Schema; use Dedoc\Scramble\Support\Generator\Types as OpenApiTypes; use Dedoc\Scramble\Support\Type\ObjectType; use Dedoc\Scramble\Support\Type\Type; use App\Exceptions\PaymentFailedException; class PaymentFailedExceptionExtension extends ExceptionToResponseExtension { public function shouldHandle(Type $type): bool { return $type instanceof ObjectType && $type->isInstanceOf(PaymentFailedException::class); } public function toResponse(Type $type): Response { return Response::make(402) ->setDescription('Payment failed') ->setContent('application/json', Schema::fromType( (new OpenApiTypes\ObjectType) ->addProperty('message', new OpenApiTypes\StringType) ->addProperty('code', new OpenApiTypes\IntegerType) ->setRequired(['message', 'code']) ) ); } public function reference(ObjectType $type): Reference { return new Reference('responses', 'PaymentFailed', $this->components); } } // Register in config/scramble.php 'extensions' => [PaymentFailedExceptionExtension::class] ``` --- ## `DocumentTransformer` — Post-Process the Full OpenAPI Document Implements `DocumentTransformer` (or passes a closure) to mutate the final OpenAPI object before it is serialised. ```php use Dedoc\Scramble\Contracts\DocumentTransformer; use Dedoc\Scramble\OpenApiContext; use Dedoc\Scramble\Scramble; use Dedoc\Scramble\Support\Generator\OpenApi; // As a class class AddSecuritySchemesTransformer implements DocumentTransformer { public function handle(OpenApi $document, OpenApiContext $context): void { $document->components->securitySchemes['bearerAuth'] = [ 'type' => 'http', 'scheme' => 'bearer', 'bearerFormat' => 'JWT', ]; $document->security[] = ['bearerAuth' => []]; } } // As a closure Scramble::afterOpenApiGenerated(function (OpenApi $document, OpenApiContext $context) { $document->info->setVersion('2.0.0'); }); // Register the class transformer Scramble::configure()->withDocumentTransformers(AddSecuritySchemesTransformer::class); ``` --- ## Artisan Commands ### `scramble:export` — Export OpenAPI Document to a File Writes the generated OpenAPI JSON spec to disk (useful for CI/CD pipelines or SDK generation). ```bash # Export default API to api.json (as configured in export_path) php artisan scramble:export # Export to a custom path php artisan scramble:export --path=public/openapi.json # Export a named API php artisan scramble:export --api=v2 --path=public/openapi-v2.json ``` ### `scramble:analyze` — Surface Documentation Errors Runs the generator in non-throwing mode and reports all routes that failed type inference. ```bash php artisan scramble:analyze # Analyze a specific named API php artisan scramble:analyze --api=v2 # Example output: # GET|HEAD api/users UserController@index # 1. Unable to infer return type for Closure # [ERROR] Found 1 errors. ``` --- ## Inline PHPDoc Annotations PHPDoc blocks inside controller methods allow granular, inline documentation of responses and parameters without PHP attributes. ```php class OrderController extends Controller { public function show(Order $order): JsonResponse { if (! $order->isPaid()) { /** * Order is not yet paid. * * @status 402 * @body array{message: string, order_id: int} */ return response()->json(['message' => 'Not paid', 'order_id' => $order->id], 402); } /** * Successful order retrieval. * * @body array{id: int, total: float, items: array<array{sku: string, qty: int}>} */ return response()->json($order->toArray()); } /** * Create a new order. * * @return array{id: int, status: string} */ public function store(StoreOrderRequest $request): JsonResponse { $order = Order::create($request->validated()); return response()->json(['id' => $order->id, 'status' => 'pending'], 201); } } ``` --- ## Access Control via Gate — `viewApiDocs` The bundled `RestrictedDocsAccess` middleware allows documentation access in `local` environment by default, and checks the `viewApiDocs` Gate policy in all other environments. ```php // In App\Providers\AppServiceProvider::boot() use Illuminate\Support\Facades\Gate; Gate::define('viewApiDocs', function ($user) { // Allow logged-in admins only return $user !== null && $user->hasRole('admin'); }); // Or: allow any authenticated user Gate::define('viewApiDocs', fn ($user) => $user !== null); // Or: allow based on IP allowlist Gate::define('viewApiDocs', function ($user, $request = null) { $allowed = ['192.168.1.0/24', '10.0.0.0/8']; return in_array(request()->ip(), $allowed); }); ``` --- ## Summary Scramble is best suited for **Laravel REST APIs** where developers want zero-annotation documentation that stays in sync with the codebase. Its static analysis engine covers the most common Laravel patterns out of the box: FormRequest validation rules become request body schemas, `JsonResource::toArray()` return types become response schemas, Eloquent paginators become paginated response wrappers, and common exceptions (`ValidationException`, `AuthenticationException`, `HttpException`, etc.) are automatically mapped to their respective error responses. The `scramble:export` command fits naturally into CI pipelines, and the Stoplight Elements UI is immediately usable in every environment. Integration patterns centre around the `Scramble` facade in a service provider's `boot()` method: use `Scramble::routes()` to scope which routes are documented, `Scramble::registerApi()` to maintain multiple separate documentation sets (e.g., a public v2 API and an internal admin API), and `Scramble::registerExtension()` to plug in custom `TypeToSchemaExtension`, `OperationExtension`, or `ExceptionToResponseExtension` classes that teach the generator how to handle project-specific response wrappers, authentication schemes, or domain exceptions. For fine-grained control over individual endpoints, PHP 8 attributes (`#[Endpoint]`, `#[Group]`, `#[Response]`, `#[QueryParameter]`, etc.) provide a clean, refactor-safe alternative to prose PHPDoc.