If you create an Eloquent model and read a generated column straight back, you get null. When you update a model the column keeps its old value until you call refresh().
Laravel 13.33 introduced the #[Refreshes] attribute to fix this potential issue: list the columns the database computes, and Eloquent reads them back after every insert and update.
Let's see how you can use this trait to automatically load generated columns after save without remembering to referesh.
Generated Columns and Stale Models
Take an orders table where the database calculates total from two other columns:
Schema::create('orders', function (Blueprint $table) { $table->id(); $table->decimal('subtotal', 10, 2); $table->decimal('tax', 10, 2); $table->decimal('total', 10, 2)->storedAs('subtotal + tax'); $table->timestamps();});
You can query and index total like any other column, but the model you get back from create() does not have it:
$order = Order::create(['subtotal' => 100, 'tax' => 8.25]); $order->total; // null $order->update(['tax' => 9.00]); $order->total; // still null
Before this release, you had to manually call $order->refresh() after each write, which reloads every column and every loaded relationship.
Adding the Refreshes Attribute
Caleb White added #[Refreshes] in #61523. Put it on the model and list the columns the database sets:
use Illuminate\Database\Eloquent\Attributes\Refreshes;use Illuminate\Database\Eloquent\Model; #[Refreshes('total')]class Order extends Model{ protected $fillable = ['subtotal', 'tax'];}
After each insert or update, Eloquent selects those columns for the saved row and copies them onto the model:
$order = Order::create(['subtotal' => 100, 'tax' => 8.25]); $order->total; // "108.25" $order->update(['tax' => 9.00]); $order->total; // "109.00"
To refresh more than one column, pass an array or several arguments. #[Refreshes(['total', 'slug'])] and #[Refreshes('total', 'slug')] do the same thing.
If you configure models with properties instead of attributes, set $refreshes:
class Order extends Model{ protected array $refreshes = ['total'];}
When a model has both, the property is used and the attribute is ignored. A model with neither runs no extra query.
Generated Values in Model Events
The refresh runs after the INSERT or UPDATE and before Eloquent fires created or updated, so listeners on either event see the new value. An observer that sends a receipt can read the total the database calculated:
class OrderObserver{ public function created(Order $order): void { Mail::to($order->customer)->send(new OrderReceipt($order)); } public function updated(Order $order): void { if ($order->wasChanged('total')) { $order->customer->notify(new OrderTotalChanged($order)); } }}
The wasChanged('total') check works because Eloquent records the changes after the refresh. Your code never sets total, but it appears in getChanges() when the database calculates a new value.
Which Writes Trigger a Refresh
The refresh runs on a model instance in these scenarios:
- Inserts from
create(),save(),saveQuietly(), andsaveOrIgnore() - Updates from
update(),save(), andsaveQuietly() - Increment/decrement calls:
increment(),decrement(),incrementEach(), anddecrementEach()
How the Refresh Query Works
The refresh is one SELECT for the listed columns, scoped to the model's primary key:
select `total` from `orders` where `id` = ? limit 1
The query runs on the write connection. With read replicas, a replica might not have the new row yet.
It also skips global scopes, which means a tenant or soft-delete scope cannot hide the row Eloquent just saved. And it uses firstOrFail(): if the row is gone by the time the query runs, the save throws a ModelNotFoundException.
Refreshes or refresh()
#[Refreshes] fits columns you read right after a write: storedAs and virtualAs columns, values that a trigger sets, and database defaults that Eloquent does not set. The attribute adds a query to every write, so list only the columns you read back.
Call $model->refresh() when you need the whole row or the loaded relationships re-read. You can also call it for a one-off write, where adding the attribute to the model would put an extra query on every other save.
The Eloquent documentation covers the attribute under "Refreshing Attributes After Writes."
Further Reading
- Tagged Memoized Cache and Model Refreshes in Laravel 13.33, the release post
- A guide to Laravel's model events
- Laravel Model Tips
- Laravel 13 Released: PHP 8.3, Attributes, Laravel AI, and a Smoother Upgrade Path, which introduced attribute-based model configuration