Skip to content

Scramble

Overview

Scramble is an automatic API documentation generator for Laravel applications. It automatically generates OpenAPI (Swagger) documentation by analyzing your routes, controllers, and form requests, providing interactive API documentation without manual specification writing.

Official Website: https://scramble.dedoc.co/


Why Scramble?

Scramble provides several benefits:

  • Automatic generation - No manual documentation writing
  • Always up-to-date - Docs reflect your actual code
  • OpenAPI 3.1.0 - Industry-standard format
  • Interactive UI - Built-in Swagger UI for testing
  • Type inference - Analyzes PHPDoc and type hints
  • Laravel integration - Understands Laravel patterns
  • Zero configuration - Works out of the box

Installation

Install Scramble

bash
# Install via Composer
composer require dedoc/scramble

# Publish configuration (optional)
php artisan vendor:publish --tag=scramble-config

Access Documentation

After installation, documentation is available at:

http://your-app.test/docs/api

Configuration

Basic Configuration

Edit config/scramble.php:

php
<?php

return [
    /*
     * API path
     */
    'api_path' => 'api',

    /*
     * API domain
     */
    'api_domain' => null,

    /*
     * Documentation route
     */
    'route' => [
        'path' => 'docs/api',
        'middleware' => ['web'],
    ],

    /*
     * Info
     */
    'info' => [
        'title' => config('app.name') . ' API',
        'description' => '',
        'version' => '1.0.0',
    ],

    /*
     * Servers
     */
    'servers' => [
        [
            'url' => config('app.url'),
            'description' => 'Default environment',
        ],
    ],
];

Production Configuration

Disable in production:

php
// In config/scramble.php
'middleware' => [
    'web',
    function ($request, $next) {
        if (app()->environment('production')) {
            abort(404);
        }
        return $next($request);
    },
],

Or use authentication:

php
'middleware' => ['web', 'auth', 'admin'],

Documenting Controllers

Basic Controller

Scramble automatically documents routes:

php
<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Post;
use Illuminate\Http\JsonResponse;

class PostController extends Controller
{
    /**
     * Get all posts
     *
     * Returns a paginated list of all posts.
     */
    public function index(): JsonResponse
    {
        $posts = Post::paginate(15);

        return response()->json([
            'data' => $posts->items(),
            'meta' => [
                'total' => $posts->total(),
                'per_page' => $posts->perPage(),
            ],
        ]);
    }

    /**
     * Get a specific post
     *
     * Returns a single post by ID.
     */
    public function show(int $id): JsonResponse
    {
        $post = Post::findOrFail($id);

        return response()->json([
            'data' => $post,
        ]);
    }
}

With Form Requests

Scramble automatically extracts validation rules:

php
<?php

namespace App\Http\Controllers\Api;

use App\Http\Requests\CreatePostRequest;
use App\Models\Post;

class PostController extends Controller
{
    /**
     * Create a new post
     */
    public function store(CreatePostRequest $request)
    {
        $post = Post::create($request->validated());

        return response()->json([
            'data' => $post,
        ], 201);
    }
}

Form Request:

php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class CreatePostRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'title' => 'required|string|max:255',
            'content' => 'required|string',
            'status' => 'required|in:draft,published',
            'published_at' => 'nullable|date',
        ];
    }
}

Type Annotations

Return Types

Use PHPDoc to specify return structures:

php
<?php

/**
 * Get user profile
 *
 * @return array{data: array{id: int, name: string, email: string}}
 */
public function profile(Request $request): JsonResponse
{
    $user = $request->user();

    return response()->json([
        'data' => [
            'id' => $user->id,
            'name' => $user->name,
            'email' => $user->email,
        ],
    ]);
}

Using Resources

Laravel API Resources are automatically documented:

php
<?php

use App\Http\Resources\PostResource;
use App\Models\Post;

class PostController extends Controller
{
    /**
     * Get all posts
     *
     * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
     */
    public function index()
    {
        return PostResource::collection(Post::paginate());
    }

    /**
     * Get a specific post
     */
    public function show(Post $post): PostResource
    {
        return new PostResource($post);
    }
}

Resource class:

php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class PostResource extends JsonResource
{
    /**
     * @param \Illuminate\Http\Request $request
     * @return array{
     *     id: int,
     *     title: string,
     *     content: string,
     *     author: AuthorResource,
     *     published_at: string|null
     * }
     */
    public function toArray($request): array
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'content' => $this->content,
            'author' => new AuthorResource($this->author),
            'published_at' => $this->published_at?->toISOString(),
        ];
    }
}

Advanced Features

Tags and Grouping

Group endpoints using PHPDoc tags:

php
<?php

namespace App\Http\Controllers\Api;

/**
 * @tags Posts
 */
class PostController extends Controller
{
    // All methods will be tagged as "Posts"
}

Or per method:

php
/**
 * Create a new post
 *
 * @tags Posts, Content Management
 */
public function store(CreatePostRequest $request)
{
    // ...
}

Operation IDs

Customize operation IDs:

php
/**
 * Get all posts
 *
 * @operationId posts.index
 */
public function index()
{
    // ...
}

Responses

Document different response codes:

php
/**
 * Create a new post
 *
 * @response 201 {"data": {"id": 1, "title": "New Post"}}
 * @response 422 {"message": "Validation failed", "errors": {}}
 */
public function store(CreatePostRequest $request)
{
    // ...
}

Authentication

Document authentication requirements:

php
<?php

namespace App\Http\Controllers\Api;

use Illuminate\Routing\Controller;

/**
 * @authenticated
 */
class PostController extends Controller
{
    // All endpoints require authentication
}

Or per route:

php
/**
 * Delete a post
 *
 * @authenticated
 */
public function destroy(Post $post)
{
    $post->delete();

    return response()->json(null, 204);
}

Security Schemes

Sanctum Bearer Token

Configure in config/scramble.php:

php
'security' => [
    [
        'name' => 'sanctum',
        'type' => 'http',
        'scheme' => 'bearer',
        'bearerFormat' => 'JWT',
    ],
],

Multiple Schemes

php
'security' => [
    [
        'name' => 'bearer_token',
        'type' => 'http',
        'scheme' => 'bearer',
    ],
    [
        'name' => 'api_key',
        'type' => 'apiKey',
        'in' => 'header',
        'name' => 'X-API-Key',
    ],
],

API Versioning

Version in Path

php
// routes/api.php
Route::prefix('v1')->group(function () {
    Route::get('/posts', [PostController::class, 'index']);
});

Route::prefix('v2')->group(function () {
    Route::get('/posts', [PostControllerV2::class, 'index']);
});

Multiple Documentation Pages

php
// config/scramble.php
'versions' => [
    'v1' => [
        'path' => 'api/v1',
        'info' => [
            'title' => 'API V1',
            'version' => '1.0.0',
        ],
    ],
    'v2' => [
        'path' => 'api/v2',
        'info' => [
            'title' => 'API V2',
            'version' => '2.0.0',
        ],
    ],
],

Custom Types

Define Custom Response Types

php
<?php

namespace App\Http\Responses;

/**
 * @property int $id
 * @property string $title
 * @property string $content
 * @property string $created_at
 */
class PostResponse
{
    // Type definition only, not used at runtime
}

Use in controller:

php
/**
 * Get a post
 *
 * @return PostResponse
 */
public function show(int $id)
{
    return Post::findOrFail($id);
}

Array Shapes

php
/**
 * @return array{
 *     data: array{
 *         id: int,
 *         title: string,
 *         author: array{id: int, name: string}
 *     },
 *     meta: array{total: int, page: int}
 * }
 */
public function index(): array
{
    // ...
}

Ignoring Routes

Ignore Specific Routes

php
// config/scramble.php
'ignore' => [
    'api/internal/*',
    'api/debug/*',
],

Ignore in Controller

php
/**
 * @hideFromAPIDocumentation
 */
public function internalMethod()
{
    // Not shown in docs
}

Examples and Testing

Adding Examples

php
/**
 * Create a post
 *
 * @bodyParam title string required Post title Example: My First Post
 * @bodyParam content string required Post content Example: This is the content
 * @bodyParam status string Post status Example: published
 */
public function store(Request $request)
{
    // ...
}

Interactive Testing

Users can test endpoints directly from the documentation UI:

  1. Click "Try it out"
  2. Fill in parameters
  3. Click "Execute"
  4. See response

Export OpenAPI Spec

Generate JSON/YAML

bash
# Export as JSON
php artisan scramble:export > openapi.json

# Export as YAML
php artisan scramble:export --format=yaml > openapi.yaml

Use with External Tools

Generated spec can be used with:

  • Postman (import OpenAPI spec)
  • Insomnia
  • API testing tools
  • Code generators

Best Practices

1. Use Type Hints

php
// ✅ Good - Type hints help Scramble
public function show(int $id): JsonResponse
{
    // ...
}

// ❌ Bad - No type information
public function show($id)
{
    // ...
}

2. Document Form Requests

php
class CreatePostRequest extends FormRequest
{
    /**
     * Validation rules for creating a post
     */
    public function rules(): array
    {
        return [
            'title' => 'required|string|max:255',
            'content' => 'required|string',
        ];
    }
}

3. Use API Resources

php
// ✅ Good - Consistent structure
return new PostResource($post);

// ❌ Bad - Inconsistent, hard to document
return response()->json($post->toArray());

4. Add Descriptions

php
/**
 * Update a post
 *
 * Updates an existing post with the provided data.
 * Only the post author or admins can update posts.
 *
 * @authenticated
 */
public function update(UpdatePostRequest $request, Post $post)
{
    // ...
}
php
/**
 * @tags User Management
 */
class UserController extends Controller
{
    // All endpoints grouped under "User Management"
}

Troubleshooting

Documentation Not Updating

bash
# Clear cache
php artisan cache:clear
php artisan config:clear
php artisan route:clear

# Force refresh browser
Ctrl+Shift+R (or Cmd+Shift+R)

Missing Endpoints

Check that routes are:

  • Under the configured api_path
  • Not in the ignore list
  • Have proper controller methods

Type Inference Issues

Add explicit PHPDoc:

php
/**
 * @return array{data: User[], meta: array{total: int}}
 */
public function index(): JsonResponse
{
    // ...
}

Integration with CI/CD

Validate Documentation

bash
# In CI pipeline
php artisan scramble:export > /dev/null

Auto-Deploy Docs

yaml
# GitHub Actions
- name: Generate API Docs
  run: php artisan scramble:export > public/openapi.json

- name: Deploy to S3
  run: aws s3 cp public/openapi.json s3://my-bucket/api-docs/


Quick Reference

Installation

bash
composer require dedoc/scramble

Access Documentation

http://your-app.test/docs/api

Export Spec

bash
php artisan scramble:export > openapi.json

Common Annotations

php
/**
 * Endpoint description
 *
 * @tags TagName
 * @authenticated
 * @operationId custom.operation.id
 * @response 200 {"data": {}}
 * @response 404 {"message": "Not found"}
 */

Configuration

php
// config/scramble.php
return [
    'api_path' => 'api',
    'route' => ['path' => 'docs/api'],
    'info' => [
        'title' => 'My API',
        'version' => '1.0.0',
    ],
];

CPR - Clinical Patient Records