Unified Data Management in Laravel with Laravel-Data
Overview of the Data Definition Problem
Modern web applications often suffer from a "multiple definition" problem. When building a feature, you typically define the same set of attributes in a form request for validation, again in an API resource for output, and a third time as a interface for your frontend. This duplication isn't just tedious; it's a breeding ground for bugs. If you add a middle_name field to your database but forget to update the API resource, your frontend breaks. , a package by , solves this by providing a single source of truth: the Data Object. This object handles validation, transformation, and type generation in one elegant class.
Prerequisites and Toolkit
To follow this guide, you should be comfortable with 8.x and the framework. You should understand the basics of if you plan on using the frontend features, and have a working knowledge of for the automated type generation components. This tutorial assumes you have a project where you're tired of writing repetitive boilerplate for requests and resources.
Key Libraries & Tools
- : The core package that creates powerful data objects to replace form requests and API resources.
- Transformer: A built-in feature of the package that scans your classes and generates matching definitions.
- : Frequently used alongside this package to bridge the gap between backend data and frontend or components.
Code Walkthrough: Implementing a Data Object
Let's replace the standard ceremony with a single object. Instead of creating a StoreContactRequest and a ContactResource, we create a ContactData class.
namespace App\Data;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Attributes\Validation\Email;
class ContactData extends Data
{
public function __construct(
public string $name,
#[Email]
public string $email,
public ?string $address,
) {}
}
By extending the Data class, this object now performs multiple roles. When used in a controller, it automatically validates incoming request data based on the property types. For example, the string $name property implies a required|string validation rule. If you want more specific constraints, use attributes like #[Email].
In your controller, you can swap out the standard request and resource calls:
public function update(ContactData $contactData, Contact $contact)
{
$contact->update($contactData->toArray());
return back();
}
public function show(Contact $contact)
{
return ContactData::from($contact);
}
The from() method is a "smart" factory. It accepts a model, an array, or a request and maps the attributes automatically. This eliminates the manual mapping usually found in .
Automated TypeScript Integration
One of the most powerful features of is the ability to keep your frontend types in sync. By running a simple command, the package scans your data objects and generates a file.
php artisan typescript:transform
This generates interfaces that match your logic exactly. If a property is nullable in (?string), it becomes optional or nullable in . This bridge ensures that your components have full auto-completion and type safety, preventing "undefined" errors at runtime.
Advanced Features: Nesting and Route Actions
Real-world data is rarely flat. supports nested data objects seamlessly. If a Project has many Guests, you can define a ProjectData class that contains a collection of GuestData objects. The transformer will respect this hierarchy, generating nested interfaces.
In the upcoming Version 4, the package introduces a Route Action Helper. This tool scans your routes and generates helpers. This allows you to call routes in your frontend using the controller name and method with full auto-completion for route parameters. You no longer need to hardcode URLs or guess which parameters a specific endpoint requires.
Tips & Gotchas
While is powerful, don't feel obligated to use it for every single interaction. Standard form requests are still excellent for simple validation logic in smaller projects. Use when you find yourself repeating the same attribute list across three or more files.
Best Practice: Always use the from() method rather than manual instantiation. It handles the heavy lifting of converting models and multi-dimensional arrays into the correct object format. If you need to hide sensitive data like addresses for certain users, use the except() or only() methods on the data object to filter the output dynamically.
- 21%· products
- 21%· languages
- 18%· products
- 11%· languages
- 5%· products
- Other topics
- 24%

Freek Van Der Herten "Enjoying Laravel Data" - Laracon US 2023 Nashville
WatchLaravel // 29:06
The official YouTube channel of Laravel, the clean stack for Artisans and agents. We will update you on what's new in the world of Laravel, from the framework to our products Cloud, Forge, and Nightwatch.