Eloquent models set default attribute values with the $attributes property. It works for fixed values, but PHP only allows constant expressions in a property's default value, so you cannot call config(), now(), or a feature flag there. Laravel 14 adds a defaults() method that returns the same array from code that runs when the model is created.
Before and After
Take a subscription model that gives new subscriptions a 14-day trial, and 30 days when an extended-trials feature flag is active. With $attributes, the flag has to be checked in the constructor:
class Subscription extends Model{ protected $attributes = [ 'cancelled' => false, 'trial_days' => 14, ]; public function __construct(array $attributes = []) { if (Feature::active('extended-trials')) { $this->attributes['trial_days'] = 30; } parent::__construct($attributes); }}
In Laravel 14, the same logic goes in defaults():
class Subscription extends Model{ protected function defaults(): array { return [ 'cancelled' => false, 'trial_days' => Feature::active('extended-trials') ? 30 : 14, 'expires_at' => now()->addDays(config('billing.grace_period'))->toDateTimeString(), ]; }}
$subscription = new Subscription; $subscription->trial_days; // 30 when the flag is active, 14 otherwise
Laravel 11 made the same move for casts, when the application skeleton switched from the $casts property to a casts() method (Model Casts are moving to methods in Laravel 11). defaults() gives $attributes a method version too.
How It Works
The model constructor calls defaults() and merges the result into $attributes. The order in the constructor decides which value wins:
- Values from the
$attributesproperty are set first defaults()is merged over them, so it wins when both define the same key- The attributes you pass to
new,create(), orfill()are applied last, so they win over both
$subscription = Subscription::create(['trial_days' => 60]); $subscription->trial_days; // 60
Defaults are merged before the model syncs its original attributes, so they do not count as dirty changes. They are still written to the database when the model is inserted, the same as $attributes.
Raw Values
Like $attributes, the values from defaults() are raw. They do not go through casts or mutators. Return the value as it would be stored in the database:
protected function defaults(): array{ return [ // A column cast to `array` expects a JSON string 'settings' => json_encode(['theme' => 'light']), // A date column 'expires_at' => now()->addDays(30)->toDateTimeString(), ];}
Returning a PHP array for a column cast to array or json fails when the attribute is read, because the cast expects a JSON string.
When defaults() Runs
The defaults() method runs every time a model instance is constructed. That includes models loaded from the database. Eloquent builds each retrieved row with a new model instance, so defaults() runs once per row, and then the row's values replace the defaults.
Upgrading
The method targets Laravel 14 because an existing model might already have a method named defaults(). In Laravel 14, the constructor calls it and merges whatever it returns into the attributes. Before upgrading, search your models for one:
grep -rn "function defaults" app/Models
Rename any method that is not meant to return default attribute values.
Jack Bayliss contributed the method in #61688. It is merged into the master branch, and it can change before Laravel 14 is released.