Introduction
Attribute casting converts raw database values to native PHP types automatically — dates to Carbon instances, JSON to arrays, integers to enums — so you don't have to remember to call json_decode every time.
Key Concepts
casts()method: The modern Laravel 11+ way to declare casts. Returns an associative array of attribute => cast type.- Primitive casts:
boolean,integer,float,double,decimal:2,string. - Date casts:
datetime,date,immutable_datetime,datetime:Y-m-d. - Array / JSON casts:
array,json,object,collection. - Enum cast: Cast to a PHP enum class, giving you type-safe comparisons.
- Custom cast class: A class implementing
CastsAttributesfor complex transformations (value objects, encrypted blobs).
Real World Context
Instead of calling json_decode($user->settings) in every controller, declare the cast once and Laravel handles it everywhere. Same for dates, booleans, and enums. This is how you get a consistent, type-safe surface on top of a relational database.
Deep Dive
Attribute casting automatically converts database values to common PHP data types. Instead of manually casting values everywhere, define casts once on your model.
Basic Casting
php<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model { /** * Get the attributes that should be cast. */ protected function casts(): array { return [ 'email_verified_at' => 'datetime', 'is_admin' => 'boolean', 'settings' => 'array', 'birthday' => 'date', 'balance' => 'decimal:2', ]; } }
Available Cast Types
Primitive Types
phpprotected function casts(): array { return [ 'is_active' => 'boolean', // true/false 'age' => 'integer', // int 'price' => 'float', // float ('double' is an alias) 'amount' => 'decimal:2', // string with 2 decimals — preferred for money 'data' => 'string', // string ]; }
Date/Time Types
phpprotected function casts(): array { return [ 'created_at' => 'datetime', // Carbon instance 'published_at' => 'datetime:Y-m-d', // Custom format 'birthday' => 'date', // Carbon (date only) 'time_slot' => 'timestamp', // Unix timestamp 'expires_at' => 'immutable_datetime', // CarbonImmutable ]; }
Usage:
php$user = User::find(1); $user->birthday; // Carbon instance $user->birthday->age; // 25 $user->birthday->format('F j, Y'); // "January 15, 1999" $user->birthday->diffForHumans(); // "25 years ago"
Array and JSON
phpuse Illuminate\Database\Eloquent\Casts\AsCollection; protected function casts(): array { return [ 'settings' => 'array', // JSON to PHP array 'preferences' => 'json', // Same as array 'metadata' => 'object', // JSON to stdClass 'collection' => 'collection', // JSON to Collection 'options' => AsCollection::class, // Same, via the dedicated cast class ]; }
Usage:
php// In database: {"theme":"dark","notifications":true} $user = User::find(1); // As array $user->settings; // ['theme' => 'dark', 'notifications' => true] $user->settings['theme']; // 'dark' // Update $user->settings = ['theme' => 'light', 'notifications' => false]; $user->save(); // Saved as JSON string // Merge values $settings = $user->settings; $settings['language'] = 'en'; $user->settings = $settings; $user->save();
Encrypted Casting
phpprotected function casts(): array { return [ 'secret' => 'encrypted', // Encrypted string 'api_keys' => 'encrypted:array', // Encrypted array 'token' => 'encrypted:collection', // Encrypted collection 'credentials' => 'encrypted:object', // Encrypted object ]; }
Data is encrypted at rest:
php$user->secret = 'my-api-key'; // Encrypted when saved $user->secret; // Decrypted when accessed: "my-api-key"
Hashed Casting (Laravel 10+)
phpprotected function casts(): array { return [ 'password' => 'hashed', ]; }
php$user->password = 'secret'; // Automatically hashed! // No need for Hash::make()
Enum Casting
Cast to PHP enums:
php// app/Enums/UserStatus.php enum UserStatus: string { case Pending = 'pending'; case Active = 'active'; case Suspended = 'suspended'; }
php// In User model protected function casts(): array { return [ 'status' => UserStatus::class, ]; }
Usage:
php$user = User::find(1); $user->status; // UserStatus::Active $user->status === UserStatus::Active; // true $user->status->value; // "active" $user->status->name; // "Active" // Set using enum $user->status = UserStatus::Suspended; // Query User::where('status', UserStatus::Active)->get();
Custom Cast Classes
For complex casting logic:
For complex casting logic, you define two classes: a value object (what callers interact with) and a cast class (the bridge between the database column and the value object).
bashphp artisan make:cast MoneyCast
php<?php // app/ValueObjects/Money.php — the plain value object namespace App\ValueObjects; final class Money { public function __construct( public readonly int $cents, public readonly string $currency = 'USD', ) {} public function format(): string { return sprintf('%.2f %s', $this->cents / 100, $this->currency); } }
php<?php // app/Casts/MoneyCast.php — the cast that bridges DB <-> Money namespace App\Casts; use App\ValueObjects\Money; use Illuminate\Contracts\Database\Eloquent\CastsAttributes; use Illuminate\Database\Eloquent\Model; class MoneyCast implements CastsAttributes { public function __construct( protected string $currency = 'USD', ) {} /** Cast the given value (from database to PHP). */ public function get(Model $model, string $key, mixed $value, array $attributes): Money { return new Money((int) $value, $this->currency); } /** Prepare the given value for storage (from PHP to database). */ public function set(Model $model, string $key, mixed $value, array $attributes): int { return $value instanceof Money ? $value->cents : (int) $value; } }
Usage on the model:
phpuse App\Casts\MoneyCast; protected function casts(): array { return [ 'price' => MoneyCast::class . ':USD', 'cost' => MoneyCast::class . ':EUR', ]; } // In the app: $product->price; // Money instance $product->price->format(); // "19.99 USD" $product->price = new Money(2500); // $25.00, stored as 2500 cents
Castable Classes
Value objects that handle their own casting:
phpuse Illuminate\Contracts\Database\Eloquent\Castable; class Address implements Castable { public static function castUsing(array $arguments): string { return AddressCast::class; } }
phpprotected function casts(): array { return [ 'address' => Address::class, ]; } // Usage $user->address = new Address('123 Main St', 'New York', 'NY'); $user->address->city; // "New York"
Common Pitfalls
- Forgetting to cast JSON columns — Without a cast,
$user->settingsreturns a string that you must manually decode. - Storing dates as strings — Without a datetime cast, comparisons and arithmetic don't work.
- Not using enum casts for fixed value sets — You lose type safety and get stuck with stringly-typed comparisons.
Best Practices
- Cast every JSON/array column — One cast, automatic decode and encode.
- Use enum casts over string matching — Type-safe, self-documenting.
- Write custom cast classes for value objects — Money, Address, Coordinates — keep them as first-class types.
Summary
- Declare casts in the
casts()method on the model. - Primitive, date, array/JSON, encrypted, and enum casts cover most needs.
- Custom cast classes (
CastsAttributesinterface) handle value objects. - Castable classes let value objects declare their own cast.
- Casts run automatically on read and write — no manual serialization.