Accessors, Mutators, and Attribute Casting
In this article, we'll walk through each of these features, showing how they let you keep formatting logic neatly organized directly within your models, instead of scattered throughout your controllers and views.
Introduction
Data rarely comes out of (or goes into) a database in exactly the format you want to work with. Maybe you store a price as cents in the database but want to display it as a formatted dollar amount. Maybe you want every title automatically capitalized before it's saved, regardless of how the user typed it. Or maybe a column stores a JSON string that you'd rather work with as a proper PHP array.
Eloquent handles all of these situations elegantly through three related features: accessors (which transform data when it's retrieved), mutators (which transform data before it's saved), and attribute casting (which automatically converts data types, like turning a database string into a PHP array or a Carbon date instance).
In this article, we'll walk through each of these features, showing how they let you keep formatting logic neatly organized directly within your models, instead of scattered throughout your controllers and views.
Accessors: Transforming Data on Retrieval
An accessor lets you customize how an attribute is returned when you access it on a model instance, without changing what's actually stored in the database.
Modern Accessor Syntax (Laravel 9+)
php
use Illuminate\Database\Eloquent\Casts\Attribute;
class Post extends Model
{
protected function title(): Attribute
{
return Attribute::make(
get: fn (string $value) => ucfirst($value),
);
}
}Now, whenever you access $post->title, Eloquent automatically runs it through this accessor:
php
$post = new Post(['title' => 'my first post']);
echo $post->title; // "My first post"A More Practical Example: Combining Fields
Accessors are especially useful for computed values that don't exist as an actual database column:
php
protected function fullName(): Attribute
{
return Attribute::make(
get: fn (mixed $value, array $attributes) => "{$attributes['first_name']} {$attributes['last_name']}",
);
}php
echo $user->full_name; // "John Smith"Even though there's no full_name column in the database, this accessor makes it available as if there were.
Mutators: Transforming Data Before Saving
A mutator works in the opposite direction — it lets you transform a value before it's actually stored in the database, ensuring consistency no matter how the data originally came in.
php
class Post extends Model
{
protected function title(): Attribute
{
return Attribute::make(
get: fn (string $value) => ucfirst($value),
set: fn (string $value) => strtolower($value),
);
}
}php
$post = Post::create(['title' => 'MY FIRST POST']);
// Stored in the database as: "my first post"
echo $post->title;
// Displayed as: "My first post" (thanks to the accessor)Notice how the same Attribute::make() definition can include both a get (accessor) and set (mutator) closure, working together to control both directions of data transformation.
A Practical Example: Hashing Passwords
One of the most common real-world uses of a mutator is automatically hashing passwords before they're saved, so you never have to remember to do it manually every time:
php
protected function password(): Attribute
{
return Attribute::make(
set: fn (string $value) => bcrypt($value),
);
}php
$user = User::create([
'name' => 'Jane Doe',
'password' => 'plaintext-password',
]);
// The password is automatically hashed before being savedAttribute Casting
While accessors and mutators are great for custom transformation logic, casting handles a more common, simpler need: automatically converting a database value into a specific PHP data type every time it's accessed, and back again when saved.
Defining Casts
php
class Post extends Model
{
protected $casts = [
'is_published' => 'boolean',
'published_at' => 'datetime',
'metadata' => 'array',
'price' => 'decimal:2',
];
}With these casts defined:
php
$post = Post::find(1);
var_dump($post->is_published); // true or false, instead of 1 or 0
echo $post->published_at->format('M d, Y'); // Works directly, since it's cast to a Carbon instance
print_r($post->metadata); // A real PHP array, instead of a raw JSON stringCommon Cast Types
php
protected $casts = [
'is_active' => 'boolean',
'settings' => 'array', // Automatically encodes/decodes JSON
'price' => 'decimal:2', // Formats decimals to a fixed number of places
'published_at' => 'datetime',
'options' => 'collection', // Casts to a Laravel Collection instead of a plain array
];Why Casting Matters for JSON Columns
Without casting, a JSON column stored in the database comes back as a raw string, requiring manual json_decode() every time you want to use it:
php
// Without casting:
$settings = json_decode($post->settings, true);
// With 'settings' => 'array' cast:
$settings = $post->settings; // Already a usable PHP arrayWhen to Use Each Feature
Use casting for straightforward type conversions — booleans, dates, arrays from JSON columns, decimals. This covers the majority of common formatting needs with minimal code.
Use accessors when you need custom read-only formatting logic, or computed values that don't map directly to a single database column (like combining
first_nameandlast_name).Use mutators when you need to guarantee data is transformed consistently before saving, regardless of where or how it's set (like hashing passwords or normalizing text formatting).
Tips for Working with Accessors, Mutators, and Casts
Reach for casting first for simple type conversions — it requires the least code and covers most common cases.
Use accessors for computed, read-only values rather than storing redundant data in the database that could be calculated on the fly.
Use mutators for data that must always be transformed consistently, like passwords or normalized formatting, so it's impossible to accidentally bypass the transformation.
Keep accessor and mutator logic simple — if the transformation becomes complex, consider moving that logic into a dedicated helper class or service instead.
Remember accessors don't change what's stored in the database — they only affect how data appears when accessed through the model.
FAQ About Accessors, Mutators, and Casting
1. What's the difference between an accessor and a cast? Casting handles straightforward, predictable type conversions (like turning 1/0 into true/false), while accessors allow fully custom logic, including combining multiple fields or applying conditional formatting.
2. Do accessors and mutators affect what's stored in the database? Mutators do — they run before data is saved. Accessors don't — they only affect how a value appears when you access it after retrieval, without altering the underlying stored value.
3. Can I cast a column to a custom class, not just built-in types like array or boolean? Yes, Laravel supports custom cast classes for more complex transformation needs, letting you define exactly how a value is cast to and from its database representation.
4. Will accessors slow down my application if I have many of them? The performance impact is generally negligible for typical use cases. Accessors only run when the specific attribute is actually accessed, not for every attribute on every query.
5. Can I combine casting with accessors and mutators on the same attribute? It's uncommon and can lead to confusing behavior, since both mechanisms transform data. Generally, choose one approach per attribute — casting for the type conversion, or an accessor/mutator for more complex logic — rather than mixing them together.
6. Is 'datetime' casting necessary if Eloquent already treats created_at and updated_at as dates? No, timestamp columns are cast to Carbon instances automatically by default. You only need to explicitly add 'datetime' casting for additional custom date columns, like published_at.
Conclusion
Accessors, mutators, and attribute casting let you keep data formatting and transformation logic neatly contained within your models, rather than scattered across controllers and views. Once defined, this logic runs consistently everywhere the model is used, reducing duplication and the risk of forgetting to apply a transformation somewhere.
In the final article of this category, we'll cover soft deletes, a feature that lets you "delete" records without permanently removing them from the database, giving you the ability to recover or audit deleted data later.
Found this helpful? Share it!