laravel-validated-dto: typed request objects with rules in the same class
Data Transfer Objects with validation for Laravel applications
At a glance
- What is it?
- This package gives Laravel a DTO base class where typed properties, validation rules, defaults and casts live together, so a controller can type hint a validated object instead of pulling fields out of a request array. It is a narrow library with a clear boundary, and the boundary is that it handles input, not persistence.
- Who is it for?
- Adopt laravel-validated-dto when your Laravel controllers are full of array access and validation that lives somewhere other than the shape of the data, since the package keeps typed properties, rules, defaults and casts in a single class and lets the container resolve a validated instance from the request. Keep it out of your persistence layer and your read models, because it is built around incoming data.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 7 days ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The DTO holds the rules, the types and the casts together
laravel-validated-dto is a Laravel package for Data Transfer Objects with validation. The problem it addresses is familiar in a Laravel codebase of any size. A controller receives a request, pulls strings out of it, and calls a validator with a rule array written somewhere else in the file. The type of the data is described in one place, the rules in another, and the conversion from string to integer or boolean in a third, often in the service layer.
The package collapses that into a single class. You extend ValidatedDTO, declare typed properties, and define the rules, the defaults and the casts in the same file:
final class UserDTO extends ValidatedDTO
{
public string $name;
public string $email;
public string $password;
public bool $active;
protected function rules(): array
{
return [
'name' => ['required', 'string'],
'email' => ['required', 'email'],
'password' => ['required', Password::min(8)],
'active' => ['sometimes', 'boolean'],
];
}
protected function defaults(): array
{
return [
'active' => true,
];
}
protected function casts(): array
{
return [
'name' => new StringCast(),
'email' => new StringCast(),
'password' => new StringCast(),
'active' => new BooleanCast(),
];
}
}Note the split of responsibilities inside the class. The rules method uses the familiar Laravel validation syntax, including the Password::min(8) rule object for a password constraint. The defaults method supplies a value for a field that was not sent, which is why active is sometimes in the rules but still has a default. The casts method maps each field to a cast object, and the property types declare what the value should look like once it has passed through. Those three are separate methods because they answer different questions: what must be present, what to assume, and what to convert.
Five ways to build a DTO, and one of them is the container
An instance can be created from several sources. The README lists an array, a request, a JSON string, an Eloquent model, or an artisan command, and the array case is the simplest:
$dto = UserDTO::fromArray([
'name' => 'John Doe',
'email' => '[email protected]',
'password' => 's3CreT!@1a2B',
]);
$dto->name; // John Doe
$dto->toArray();The interesting case is the last one in the list, because it removes the construction call from your controller entirely. Instead of resolving a DTO yourself, you type hint it in the action signature and let the service container build it from the current request:
use Illuminate\Http\JsonResponse;
final class StoreUserController
{
public function __invoke(UserDTO $dto): JsonResponse
{
return response()->json($dto->toArray());
}
}The practical effect is that a controller action no longer contains the sequence build, validate, extract. The container resolves the DTO, validation runs as part of that resolution, and a failed validation produces the framework's usual validation error response rather than something you had to raise yourself. The type of the argument is also the documentation of the endpoint, which matters when someone reads the signature without opening the DTO class.
This is the part of the design that makes the package worth the dependency. Writing the DTO is the same effort as writing a Form Request, but the object is a plain value with typed properties rather than a request wrapper, so it can be constructed outside a request as well.
Installing the package and publishing its configuration
Installation is a single Composer require:
composer require wendelladriel/laravel-validated-dtoThe package ships a config file that can be published into your application:
php artisan vendor:publish --tag="validated-dto"Publishing matters more here than it does for a package with one boolean option, because the config is where the package's own defaults live. Once the file is in your repository, a reader can see what the library does when a field is missing or a value fails to cast, and you can change it without waiting for an upstream release.
The documentation site is separate from the repository README and covers the changelog as well, which is the place to look when you upgrade and want to know what changed between 4.5.0, 4.6.0 and 4.7.0. The README itself is short by design and points at that site for the full documentation.
The rest of the repository is a normal PHP package layout: src for the code, config for the configuration, tests with a phpunit.xml, a Makefile, a pint.json for code style, a docker directory with a docker-compose.yml for running the toolchain in a container, a docs directory, and resources. The docs are built with VitePress, with package.json holding only the docs scripts and vitepress as the single dev dependency, since the documentation site is a static build rather than a Node application.
The project runs its own toolchain inside Docker
The Makefile in the repository is a thin wrapper over Docker Compose, and its targets describe the development loop the maintainer actually uses. The Docker section has start, stop and build, and the application section has install, lint, test-lint, test-unit and test, each delegating to a composer command inside the running container.
The make install target, for example, runs composer install in the container:
docker compose run --rm app composer installand the test target runs the linter and the test suite together:
docker compose run --rm app composer testThe compose file builds an image from docker/Dockerfile, mounts the working directory into the container at /usr/src/app, and keeps stdin open with a tty so interactive commands work. The result is that a contributor needs Docker and nothing else: no local PHP version matching the project, no composer install on the host, no divergence between a contributor's machine and CI.
For a team that already has a working PHP toolchain, this is overhead. For a team that wants a reproducible environment without managing PHP versions by hand, it is a reasonable trade, and the choice is visible in the fact that the whole development surface is three make targets.
Where the package stops, and what it does not do
The boundary is input. Every example in the documentation is about data arriving from outside and being validated and cast before the application touches it. The package does not describe how to persist a DTO, how to transform one into an Eloquent model, or how to serialise a model back out. If your application needs a typed object for reading from the database, a resource or a plain model is the tool for that, and the DTO adds nothing.
A second limit is worth naming. The DTO is created from the request, and the request is the input side of a request and response cycle. Making a DTO a response shape means you are using a class designed for untrusted input as an output contract, and the casts and defaults that make sense on the way in may not make sense on the way out. Nothing stops you, but the design pulls you towards input only.
The third limit is that validation rules are the ones you write, so the package does not remove the need to think about what a valid request is. It moves the rules next to the type declaration, which is a real improvement in readability, but it does not decide the rules for you. The Password::min(8) in the example is a choice the developer made, not a default the package supplies.
Compared with Laravel's own form requests, the difference in approach is the return value. A form request validates and gives you a request object. This package validates and gives you a typed value object with public typed properties, which you can pass to a service without the array access that tends to follow a request around.
Editorial conclusion
Adopt laravel-validated-dto when your Laravel controllers are full of array access and validation that lives somewhere other than the shape of the data, since the package keeps typed properties, rules, defaults and casts in a single class and lets the container resolve a validated instance from the request. Keep it out of your persistence layer and your read models, because it is built around incoming data. Before you upgrade, check the changelog for the version boundary, since 4.7.0 shipped on 2026-05-26 and the last push was on 2026-10-16, and publish the config file so your defaults are visible in your own repository.
Frequently asked questions
What is laravel-validated-dto and what does it do?
It is a Laravel package for Data Transfer Objects with validation. You extend ValidatedDTO, declare typed properties, and define the validation rules, the defaults and the casts in the same class, then create instances from an array, a request, a JSON string, an Eloquent model or an artisan command.
How do I install laravel-validated-dto?
Run composer require wendelladriel/laravel-validated-dto. You can also publish the package config file with php artisan vendor:publish --tag="validated-dto", which puts the library defaults into your own repository where they can be changed and reviewed.
How do I get a validated DTO into a Laravel controller?
Type hint the DTO class in the controller action signature and let the service container resolve it from the current request, so the controller receives a validated typed object rather than building one itself. The full documentation is at laravel-validated-dto.wendelladriel.com.
Does laravel-validated-dto replace Laravel form requests?
It covers a similar validation job but returns a typed value object with public properties instead of a request wrapper, so it can be built outside a request too. It is designed for incoming data, so it is not a replacement for resources or models used for reading and output.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/wendelladriel-laravel-validated-dto)Was this useful?