name: laravel-validated-dto-development description: Guidance for generating, designing, and refactoring ValidatedDTO, SimpleDTO, and ResourceDTO classes in Laravel applications. metadata:
Laravel Validated DTO
Use this skill when working with wendelladriel/laravel-validated-dto: generating DTO classes, improving existing DTOs, applying attributes and casts, or replacing FormRequest validation with DTOs.
Choose the right DTO type
ValidatedDTO: use when incoming data must be validated before the DTO is considered ready.SimpleDTO: use when you want typed data, casts, mapping, and transforms without validation.ResourceDTO: use when the DTO is primarily an API response object and should be returned directly from controllers.
Default to ValidatedDTO unless the code clearly does not need validation or is strictly response-only.
Generate DTOs with commands
Use the package commands instead of hand-writing boilerplate first:
php artisan make:dto UserDTO
php artisan make:dto CheckoutInputDTO --simple
php artisan make:dto UserResourceDTO --resource
- Generated classes go to
App\DTOsby default. - The namespace comes from
config/dto.phpviadto.namespace. - If the project needs custom templates, publish the stubs first:
php artisan dto:stubs
That creates customizable stubs in stubs/ for validated, simple, and resource DTOs.
Authoring high-quality DTOs
Core rules
- Declare public typed properties for every field the DTO owns.
- Keep validation rules, default values, casts, and mapping aligned with those property types.
- Prefer explicit casts for non-trivial types, nested objects, collections, enums, dates, and numeric string inputs.
- Use
nullableoroptionalrules when a property can legitimately becomenull. - Keep DTOs focused on transport and normalization, not business logic.
Required methods
For generated DTOs, these are the main hooks:
rules(): validation rules forValidatedDTOonly.defaults(): fallback values when data is missing.casts(): property casting definitions.
Add these only when needed:
messages(): custom validation messages.attributes(): human-friendly attribute names.mapData(): map external input keys before validation/assignment.mapToTransform(): map DTO keys beforetoArray(),toJson(), ortoModel().
Prefer attributes for concise DTOs
Use attributes when rules, casts, defaults, or mapping are property-local and easy to read inline.
Available attributes
#[Rules([...], messages: [...])]: define validation rules per property.#[DefaultValue(...)]: define fallback values.#[Cast(Type::class, param: OtherType::class)]: define casts inline.#[Map(data: 'incoming_key', transform: 'outgoing_key')]: map input/output names.#[Receive(PropertyCase::SnakeCase)]: accept a naming convention for all incoming properties.#[Provide(PropertyCase::PascalCase)]: transform all outgoing properties to a naming convention.#[Lazy]: defer validation and casting untilvalidate()is called.#[SkipOnTransform]: exclude a property fromtoArray(),toJson(), andtoModel().
Traits that pair well with attributes
If a DTO is fully attribute-driven, use these traits so the class can stay minimal:
EmptyRulesEmptyDefaultsEmptyCasts
This pattern is especially useful for attribute-heavy ValidatedDTO classes.
Casting guidance
Use built-in casts whenever possible:
- scalars:
StringCast,IntegerCast,FloatCast,BooleanCast - structures:
ArrayCast,CollectionCast,ObjectCast - dates:
CarbonCast,CarbonImmutableCast - domain types:
DTOCast,ModelCast,EnumCast
Strong defaults:
- Use
DTOCastfor nested DTO objects. - Use
CollectionCast(new DTOCast(...))orArrayCast(new DTOCast(...))for nested lists. - Use
EnumCastfor PHP enums instead of manual string handling. - Use a custom
Castableimplementation or callable cast when the transformation is project-specific.
If config('dto.require_casting') is true, every DTO property must have a cast or instantiation will fail.
Mapping guidance
Use method-based mapping for complex or nested remapping, and attributes for simple one-property renames.
mapData(): input normalization before validation.mapToTransform(): output normalization before array/json/model export.#[Receive(...)]and#[Provide(...)]: bulk casing conversion for all properties.#[SkipOnTransform]: keep internal-only fields out of exported payloads.
Use nested mapping when the external payload shape and the internal DTO shape intentionally differ.
Working with runtime sources
DTOs can be created from several sources:
$dto = UserDTO::fromArray($data);
$dto = UserDTO::fromJson($json);
$dto = UserDTO::fromRequest($request);
$dto = UserDTO::fromModel($user);
$dto = UserDTO::fromCommandArguments($this);
$dto = UserDTO::fromCommandOptions($this);
$dto = UserDTO::fromCommand($this);
For controllers in Laravel, prefer type-hinting the DTO directly when the package is already wired into the app:
public function store(UserDTO $dto)
{
// ...
}
Livewire and lazy validation
When a DTO is filled progressively, prefer lazy validation:
- set
public bool $lazyValidation = true, or - add
#[Lazy]to the DTO class.
Call $dto->validate() when the DTO is ready.
For Livewire integration, use the Wireable trait so the DTO can move between Livewire and PHP safely.
Converting FormRequests into DTOs
When asked to convert a FormRequest into a DTO, use this migration approach:
- Create a
ValidatedDTOwith public typed properties matching the validated payload. - Move
rules()from theFormRequestinto the DTO. - Move
messages()andattributes()if the request defines them. - Convert
prepareForValidation()style key normalization intomapData()or#[Map(...)]when possible. - Convert output reshaping into
mapToTransform()or#[Map(transform: ...)]when needed. - Add casts for booleans, integers, floats, enums, nested DTOs, collections, models, and dates.
- Replace controller signatures like
store(StoreUserRequest $request)withstore(StoreUserDTO $dto)when the app is using DTO auto-resolution. - Replace usages of
$request->validated()with DTO properties.
Important differences from FormRequest:
- Authorization does not live in the DTO; keep that in policies, middleware, or controller/application logic.
- DTOs are reusable outside HTTP, so prefer keeping request-only concerns out of them.
- File validation rules can stay in the DTO; uploaded files are supported.
Refactoring checklist
When creating or updating a DTO, verify that:
- property types, rules, defaults, and casts agree with each other
- nullable fields have matching validation rules
- nested data uses
DTOCast,CollectionCast, orArrayCastcorrectly - mapping is symmetrical only when it needs to be
- exported payloads do not leak internal-only properties
- the chosen DTO base class matches the actual use case
Good defaults to follow
- Prefer attributes for local property concerns.
- Prefer methods for cross-field or nested mapping.
- Prefer explicit casts over relying on raw input types.
- Prefer
ResourceDTOonly for response objects. - Prefer DTO injection over manually duplicating request validation in controllers.