Eloquent model events in Laravel 13 fire at points in a model's lifecycle: creating, created, updating, updated, saving, saved, deleting, deleted, restoring and restored. You can listen with closures in the model's booted() method or, for cleaner code, with an observer class generated by php artisan make:observer.
What do you need before you start?
- Laravel 13 on PHP 8.3 or newer
- A
Postmodel withtitle,sluganduser_idcolumns
Which events does Eloquent fire?
- retrieved: after a model is read from the database
- creating / created: around the first insert
- updating / updated: around an update of an existing row
- saving / saved: around both create and update
- deleting / deleted: around a delete
- restoring / restored: for soft-deleted models
- replicating: when a model is replicated
The "ing" events run before the database write. Returning false from a before-event stops the operation.
How do you listen with closures?
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class Post extends Model
{
protected $fillable = ['title', 'slug', 'user_id'];
protected static function booted(): void
{
static::creating(function (Post $post) {
$post->slug ??= Str::slug($post->title);
});
static::deleted(function (Post $post) {
logger()->info('Post deleted', ['id' => $post->id]);
});
}
}How do you create an observer?
php artisan make:observer PostObserver --model=Post<?php
namespace App\Observers;
use App\Models\Post;
use Illuminate\Support\Str;
class PostObserver
{
public function creating(Post $post): void
{
$post->slug ??= Str::slug($post->title);
}
public function updated(Post $post): void
{
if ($post->wasChanged('title')) {
logger()->info('Post title changed', ['id' => $post->id]);
}
}
public function deleted(Post $post): void
{
// clean up related files, caches, etc.
}
}Laravel supports registering the observer with the ObservedBy attribute on the model. You can also call Post::observe(PostObserver::class) in a service provider.
use App\Observers\PostObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
#[ObservedBy([PostObserver::class])]
class Post extends Model
{
// ...
}When do events NOT fire?
Events fire only when you work through model instances. Bulk queries skip them, because no model is loaded.
Post::where('user_id', 5)->update(['title' => 'x']); // no updated event
Post::where('user_id', 5)->delete(); // no deleted event
Post::insert([...]); // no created event
Post::upsert([...], ['slug'], ['title']); // no eventsIf you need events, load the models first (for example with each()), accepting the extra queries.
How do you silence events temporarily?
Post::withoutEvents(function () {
Post::create(['title' => 'Seeded', 'slug' => 'seeded', 'user_id' => 1]);
});
$post->saveQuietly();Should observers dispatch queued work?
Yes, for slow tasks. Dispatch a job from the observer instead of doing the work inline. If the job depends on committed data, implement ShouldHandleEventsAfterCommit on the observer so it runs after the database transaction commits.
How do you verify it works?
public function test_slug_is_generated_on_create(): void
{
$post = Post::create(['title' => 'Hello World', 'user_id' => User::factory()->create()->id]);
$this->assertSame('hello-world', $post->slug);
}What are the common mistakes?
- Expecting events from
update()on a query builder - Heavy work inside
saving, which slows every write - Observers that touch the model again and loop
- Forgetting that seeders and factories also trigger events unless silenced
FAQ
Closure listeners or an observer?
Use closures for one or two tiny hooks. Use an observer when logic grows or needs tests.
What is the difference between saving and creating?
saving fires for both inserts and updates. creating fires only for new rows.
How do I stop an operation in a before-event?
Return false from the listener for events such as creating or deleting.



