Paul Redmond reported the change for Laravel News on October 1, 2026. Laravel 14 adds a protected defaults(): array method to Eloquent models. The $attributes property remains suitable for fixed values, but PHP allows only constant expressions in property defaults. Runtime calls such as config(), now(), and feature checks therefore need another place.

Subscription.php
<?php

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(),
        ];
    }
}

The example uses a Subscription model. It keeps cancelled set to false and gives new subscriptions 14 trial days. Feature::active('extended-trials') can raise that value to 30. The method also calculates expires_at with now()->addDays(config('billing.grace_period'))->toDateTimeString(). The earlier approach checked the feature flag in __construct and changed trial_days before calling parent::__construct($attributes).

Eloquent merges defaults during construction. Values from $attributes are loaded first, defaults() overrides matching keys, and values supplied to new, create(), or fill() are applied last. Subscription::create(['trial_days' => 60]) therefore retains 60. The merge occurs before Eloquent synchronizes original attributes, so these defaults are not marked dirty. They are still inserted into the database like values from $attributes.

Values returned by defaults() are raw. Casts and mutators do not process them at this stage. A settings column with an array or JSON cast needs a JSON string such as json_encode(['theme' => 'light']). A date value can use now()->addDays(30)->toDateTimeString(). Returning a PHP array for an array or JSON cast causes reading the attribute to fail because the cast expects JSON.

The method runs whenever Eloquent constructs a model, including while hydrating rows from the database. Each retrieved row gets a new model instance, and the row's stored values then replace the generated defaults.

Laravel 11 made a comparable change for casts by moving the application skeleton from the $casts property to casts(). Laravel 14 introduces defaults() partly because an existing model may already define a method with that name. Developers should search with `grep -rn "function defaults" app/Models` and rename methods that do not return default attributes. Jack Bayliss contributed the change in PR #61688. It is merged into the master branch and may still change before Laravel 14 is released.