Generate beautiful API documentation automatically from Spatie's Laravel Data classes. This package integrates seamlessly with Knuckles Scribe to extract request and response schemas directly from your Data DTOs, eliminating manual documentation of API parameters.
- Automatic Parameter Extraction: Automatically generates API documentation from Laravel Data classes
- Smart Attributes: Control documentation with
#[Hidden],#[Example],#[QueryParameter], and#[ResponseData]attributes - Laravel Data Validation Support: Automatic OpenAPI documentation for 30+ built-in Laravel Data validation attributes (
#[Email],#[Min],#[Max],#[Uuid], etc.) - Nested Objects Support: Handles nested Data objects and arrays with automatic dot notation and array notation
- Type-Safe: Leverages PHP 8.1+ attributes and Laravel Data's type system
- Extensible Architecture: Pipeline-based processing with custom stages, attribute processors, and type processors
- Scribe Integration: Works seamlessly with Knuckles Scribe for OpenAPI/Swagger documentation
- Zero Configuration: Works out of the box with sensible defaults
You can install the package via composer:
composer require abrha/laravel-data-docsRegister the package strategies in your config/scribe.php:
return [
// ... other config
'openapi' => [
'enabled' => true,
// IMPORTANT: Add the extended OpenAPI generator to properly merge
// validation rules, formats, patterns, and other OpenAPI properties
// extracted from Laravel Data attributes into the final specification
'generators' => [
\Abrha\LaravelDataDocs\OpenApi\ExtendedOpenApiGenerator::class,
],
],
'strategies' => [
'bodyParameters' => [
\Abrha\LaravelDataDocs\Strategies\BodyParameters\GetFromRequestDTOStrategy::class,
// ... other strategies
],
'queryParameters' => [
\Abrha\LaravelDataDocs\Strategies\QueryParameters\GetFromRequestDTOStrategy::class,
// ... other strategies
],
'responseFields' => [
\Abrha\LaravelDataDocs\Strategies\ResponseDataStrategy::class,
// ... other strategies
],
],
];Optionally, publish the config file:
php artisan vendor:publish --tag="laravel-data-docs-config"use Spatie\LaravelData\Data;
use Abrha\LaravelDataDocs\Attributes\Example;
use Abrha\LaravelDataDocs\Attributes\Hidden;
class CreateUserRequest extends Data
{
public function __construct(
#[Example('john.doe@example.com')]
public string $email,
#[Example('John Doe')]
public string $name,
public int $age,
#[Hidden]
public bool $isAdmin = false,
) {}
}
class UserResponse extends Data
{
public function __construct(
public int $id,
public string $email,
public string $name,
public int $age,
public string $created_at,
) {}
}use Abrha\LaravelDataDocs\Attributes\ResponseData;
class UserController extends Controller
{
#[ResponseData(UserResponse::class)]
public function store(CreateUserRequest $request)
{
$user = User::create($request->all());
return UserResponse::from($user);
}
#[ResponseData(UserResponse::class)]
public function show(User $user)
{
return UserResponse::from($user);
}
}php artisan scribe:generateThat's it! Scribe will automatically extract request parameters from CreateUserRequest and response structure from UserResponse.
The package automatically processes Laravel Data validation attributes to generate comprehensive OpenAPI documentation with validation rules, formats, and descriptions.
#[Hidden]
Excludes a property from generated documentation. Useful for internal flags, metadata, or sensitive fields.
use Abrha\LaravelDataDocs\Attributes\Hidden;
class UserData extends Data
{
public function __construct(
public string $name,
#[Hidden]
public bool $internalFlag = false,
#[Hidden]
public ?string $debugInfo = null,
) {}
}Provides custom example values for API documentation instead of auto-generated examples.
use Abrha\LaravelDataDocs\Attributes\Example;
class ProductData extends Data
{
public function __construct(
#[Example('Premium Widget')]
public string $name,
#[Example(29.99)]
public float $price,
#[Example(['electronics', 'gadgets'])]
public array $tags,
#[Example(true)]
public bool $inStock,
) {}
}Marks a property as a URL query parameter (for non-GET requests). GET requests treat all parameters as query parameters by default.
use Abrha\LaravelDataDocs\Attributes\QueryParameter;
class SearchRequest extends Data
{
public function __construct(
#[QueryParameter]
public string $search,
#[QueryParameter]
public int $page = 1,
#[QueryParameter]
public int $perPage = 15,
// This will be a body parameter
public array $filters = [],
) {}
}Specifies the response DTO class for a controller method. Applied to controller methods, not Data classes.
use Abrha\LaravelDataDocs\Attributes\ResponseData;
class PostController extends Controller
{
#[ResponseData(PostResponse::class)]
public function index()
{
return PostResponse::collection(Post::paginate());
}
#[ResponseData(PostResponse::class)]
public function store(CreatePostRequest $request)
{
$post = Post::create($request->all());
return PostResponse::from($post);
}
}The package has built-in support for Laravel Data validation attributes. These automatically add OpenAPI validation rules, formats, and descriptions:
Format Attributes:
#[Email]- Adds format: email#[Url],#[ActiveUrl]- Adds format: uri#[Uuid]- Adds format: uuid#[Password]- Adds format: password#[IPv4],#[IPv6],#[IP]- Adds IP format validation#[Date]- Adds format: date#[DateFormat]- Adds custom date format pattern#[Json]- Adds format: json
String Pattern Attributes:
#[Alpha]- Only letters#[AlphaNumeric]- Letters and numbers#[AlphaDash]- Letters, numbers, dashes, underscores#[Lowercase]- Lowercase letters only#[Uppercase]- Uppercase letters only#[Ulid]- ULID pattern validation#[Regex]- Custom regex pattern#[StartsWith],#[EndsWith]- String prefix/suffix validation
Numeric Validation:
#[Min],#[Max]- Minimum/maximum values#[Between]- Value between min and max#[GreaterThan],#[GreaterThanOrEqualTo]- Comparison validation#[LessThan],#[LessThanOrEqualTo]- Comparison validation#[MultipleOf]- Must be multiple of value#[Digits]- Exact number of digits#[DigitsBetween]- Digits between min and max
Example:
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Attributes\Validation\Email;
use Spatie\LaravelData\Attributes\Validation\Min;
use Spatie\LaravelData\Attributes\Validation\Max;
use Spatie\LaravelData\Attributes\Validation\Uuid;
class CreateUserRequest extends Data
{
public function __construct(
#[Email]
public string $email,
#[Min(3), Max(50)]
public string $name,
#[Min(18), Max(120)]
public int $age,
#[Uuid]
public string $organizationId,
) {}
}This automatically generates OpenAPI documentation with:
- Email format validation
- Name length constraints (3-50 characters)
- Age numeric constraints (18-120)
- UUID format for organizationId
The package automatically handles nested Data objects with dot notation:
class AddressData extends Data
{
public function __construct(
public string $street,
public string $city,
public string $zipCode,
) {}
}
class UserData extends Data
{
public function __construct(
public string $name,
public AddressData $address,
) {}
}Generated parameters:
name(string)address(object)address.street(string)address.city(string)address.zipCode(string)
Arrays of Data objects are automatically handled with array notation:
class OrderItemData extends Data
{
public function __construct(
public int $productId,
public int $quantity,
public float $price,
) {}
}
class OrderData extends Data
{
public function __construct(
public string $customerName,
/** @var OrderItemData[] */
public array $items,
) {}
}Generated parameters:
customerName(string)items(object[])items[].productId(integer)items[].quantity(integer)items[].price(number)
The package provides four extension methods, ordered from simplest to most advanced:
The easiest way to customize documentation is through the config file. Define custom type mappings without writing any code:
// config/data-docs.php
return [
'custom_types' => [
App\ValueObjects\UUID::class => [
'type' => 'string',
'descriptions' => ['A valid UUID v4 identifier'],
'pattern' => '^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$',
'format' => 'uuid',
],
App\ValueObjects\PhoneNumber::class => [
'type' => 'string',
'descriptions' => ['International phone number in E.164 format'],
'pattern' => '^\+[1-9]\d{1,14}$',
'minLength' => 10,
'maxLength' => 15,
],
],
];Available configuration fields:
type: OpenAPI type (string, integer, number, boolean, array, object)descriptions: Array of description stringspattern: Regex pattern for validationformat: OpenAPI format (uuid, email, date-time, etc.)minimum: Minimum value for numbersmaximum: Maximum value for numbersexclusiveMinimum: Exclusive minimum valueexclusiveMaximum: Exclusive maximum valueminLength: Minimum string lengthmaxLength: Maximum string lengthminItems: Minimum array itemsmaxItems: Maximum array itemsmultipleOf: Number must be multiple of this value
When to use: Simple type mappings where you just need to specify OpenAPI properties declaratively.
For more dynamic control over type processing, implement a CustomTypeProcessor:
use Abrha\LaravelDataDocs\CustomTypeProcessing\CustomTypeProcessor;
use Abrha\LaravelDataDocs\Pipeline\Context\ParameterContext;
class UlidProcessor implements CustomTypeProcessor
{
public function process(string $className, ParameterContext $context): void
{
$context->type = 'string';
$context->descriptions = ['A valid ULID identifier'];
$context->example = '01ARZ3NDEKTSV4RRFFQ69G5FAV';
// You can add dynamic logic here
// Access other context properties, generate examples, etc.
}
}Register in your AppServiceProvider:
use Abrha\LaravelDataDocs\CustomTypeProcessing\CustomTypeProcessorRegistry;
use Symfony\Component\Uid\Ulid;
public function register()
{
$registry = CustomTypeProcessorRegistry::getInstance();
$registry->register(Ulid::class, new UlidProcessor());
}When to use: When you need programmatic control over type documentation or need to generate dynamic examples/descriptions based on the class itself.
Process custom or existing validation attributes by implementing an AttributeProcessor. This allows you to create reusable attributes that modify documentation behavior.
Step 1: Create your custom attribute:
use Abrha\LaravelDataDocs\Attributes\DataDocsAttribute;
use Attribute;
#[Attribute(Attribute::TARGET_PROPERTY)]
class InEnumCases implements DataDocsAttribute
{
public function __construct(
public readonly string $enumClass,
) {}
}Step 2: Create the processor:
use Abrha\LaravelDataDocs\AttributeProcessing\AttributeProcessor;
use Abrha\LaravelDataDocs\Pipeline\Context\ParameterContext;
class InEnumCasesProcessor implements AttributeProcessor
{
public function process(object $attribute, ParameterContext $context): void
{
$enumClass = $attribute->enumClass;
$cases = array_map(fn($case) => $case->value, $enumClass::cases());
$context->descriptions[] = 'Must be one of: ' . implode(', ', $cases);
$context->example = $cases[0] ?? null;
// You can modify any aspect of the context
// Add to descriptions, set examples, modify type, etc.
}
}Step 3: Register in your AppServiceProvider:
use Abrha\LaravelDataDocs\AttributeProcessing\AttributeProcessorRegistry;
public function register()
{
$attributeRegistry = AttributeProcessorRegistry::getInstance();
$attributeRegistry->register(InEnumCases::class, new InEnumCasesProcessor());
}Step 4: Use in your Data classes:
use App\Enums\UserStatus;
class UserData extends Data
{
public function __construct(
public string $name,
#[InEnumCases(UserStatus::class)]
public string $status,
) {}
}When to use: When you need reusable, attribute-based validation logic that can be applied to multiple properties across different Data classes. Great for domain-specific validations.
For complete control over the processing pipeline, implement custom stages. This gives you access to the full context and allows modification at any point in the pipeline.
use Abrha\LaravelDataDocs\Pipeline\ParameterPipelineStage;
use Abrha\LaravelDataDocs\Pipeline\Context\ParameterContext;
class CustomValidationStage implements ParameterPipelineStage
{
public function process(ParameterContext $context): ParameterContext
{
// Full access to context - can modify any aspect
$attributes = $context->propertyAttrs;
// Complex custom logic here
if (isset($attributes[Deprecated::class])) {
$deprecated = $attributes[Deprecated::class];
$context->descriptions[] = "**DEPRECATED**: {$deprecated->reason}";
}
// You can even stop processing by setting isHidden
if ($this->shouldHideProperty($context)) {
$context->isHidden = true;
}
return $context;
}
private function shouldHideProperty(ParameterContext $context): bool
{
// Complex hiding logic based on multiple factors
return false;
}
}Register your stage in the pipeline:
use Abrha\LaravelDataDocs\Pipeline\PipelineFactory;
// Note: You'll need to customize how you create the pipeline
// This typically happens in a service provider or strategy
$pipeline = PipelineFactory::createDefault()
->addStage(new CustomValidationStage());When to use: When you need to:
- Implement complex cross-cutting concerns
- Modify processing behavior based on multiple factors
- Add entirely new processing steps to the pipeline
- Have full control over the documentation generation flow
Note: This requires deeper understanding of the package internals and how the pipeline processes contexts.
| Method | Complexity | Use Case |
|---|---|---|
| Config File | ⭐ Simple | Static type mappings |
| Type Processor | ⭐⭐ Moderate | Dynamic type-specific logic |
| Attribute Processor | ⭐⭐⭐ Moderate-Advanced | Reusable attribute-based validation |
| Pipeline Stage | ⭐⭐⭐⭐ Advanced | Complex cross-cutting concerns |
The package uses a pipeline pattern to process each property in your Data classes:
- Context Creation: A
ParameterContextobject is created for each property - Pipeline Processing: The context flows through multiple stages:
HiddenStage: Checks for#[Hidden]attributeTypeStage: Determines base type from PHP type hintsCustomTypeStage: Applies custom type configurationsAttributeProcessingStage: Processes all attributes using registered AttributeProcessors (includes 30+ built-in Laravel Data validation attributes)TypeDescriptionStage: Generates type descriptionsDefaultValueStage: Extracts default valuesDefaultValueDescriptionStage: Generates default value descriptionsRequiredStage: Determines if field is requiredExampleGenerationStage: Generates example values using Faker
- Parameter Generation: Context is converted to API parameter format
- OpenAPI Enhancement: The ExtendedOpenApiGenerator merges additional OpenAPI schema information into the final specification
The package provides three Scribe strategies and an OpenAPI generator:
- BodyParameters Strategy: Extracts request body parameters from Data classes
- QueryParameters Strategy: Extracts query parameters (GET requests or properties marked with
#[QueryParameter]) - ResponseData Strategy: Extracts response structure from Data classes marked with
#[ResponseData] - ExtendedOpenApiGenerator: Merges custom OpenAPI schema information (default values, formats, patterns, etc.) extracted from Laravel Data attributes into the generated OpenAPI specification
These strategies automatically detect Data classes in your controller methods and generate comprehensive documentation with validation rules.
This project uses a Docker-based development environment with a long-running container for fast command execution.
Start the development container (do this once):
./dev upThe container stays running in the background. All subsequent commands execute instantly without container startup overhead.
./dev pest # Run all tests
./dev pest tests/Unit/SomeTest.php # Run specific test
./dev pint # Format all files
./dev pint src/ # Format specific directory
./dev composer require package/name # Install packages
./dev shell # Interactive shell
./dev down # Stop container when doneRun ./dev help to see all available commands.
Run all tests:
./dev pestRun tests with coverage:
./dev coverageOr using composer:
composer test-coveragePlease see CHANGELOG for more information on what has changed recently.
Please see CONTRIBUTING for details.
Please review our security policy on how to report security vulnerabilities.
The MIT License (MIT). Please see License File for more information.