Laravel Eloquent ORM, Part 8: Model Events and Observers
Welcome to Part 8 of the Laravel Eloquent ORM series. Every time a model is created, updated, or deleted, Eloquent fires an event behind the scenes. This part covers how to hook into those moments, and when to organize that logic with an Observer instead.
What Are Model Events and Observers?
A model event is a signal Eloquent fires at a specific point in a model’s life, moments like creating, created, updating, deleted, and a few others. You can listen for any of these and run your own code right when they happen, without changing how the model is normally used.
An Observer is simply a dedicated class for holding that listening code, one method per event, instead of writing it all inline inside the model. Same events, just organized in their own place.
With that in mind, here’s when you’d actually reach for each one.
Scenario 1: Auto-Generating a Slug on Creation
The situation: Every post needs a URL-friendly slug generated from its title. Doing this manually in every controller that creates a post is easy to forget.
The fix: Hook into the creating event, right before a record is saved for the first time. Add this inside the model's boot() method:
class Post extends Model
{
protected static function boot()
{
parent::boot();
static::creating(function ($post) {
$post->slug = Str::slug($post->title);
});
}
}Now, no matter where a post gets created from, admin panel, API, seeder, the slug is always generated. One place, always applied.
Scenario 2: Sending a Welcome Email Without Cluttering the Controller
The situation: After a user registers, you need to send a welcome email. Putting that directly in the registration controller works, but it mixes registration logic with email logic, and gets messier as more “after registration” tasks pile up.
static::created(function ($user) {
Mail::to($user->email)->send(new WelcomeEmail($user));
});This runs automatically after every user is created, regardless of which controller or command created it.
Get Alamgir Hosen’s stories in your inbox
Join Medium for free to get updates from this writer.
When boot() Gets Crowded: Observers
The situation: Once a model needs several event hooks, creating, updating, deleting, all stacked inside boot(), that method turns into a wall of nested closures that's hard to read and test.
An Observer moves each event into its own clearly named method, in its own class.
class PostObserver
{
public function creating(Post $post)
{
$post->slug = Str::slug($post->title);
}
public function deleted(Post $post)
{
Log::info("Post deleted: {$post->id}");
}
}Register it in a service provider:
Post::observe(PostObserver::class);Same behavior as before, just organized into readable, individually testable methods instead of one growing closure block.
Which One Should You Use?
One or two simple event hooks on a model: Keep it inline in boot(). No need to create a whole extra class for one line of logic.
Three or more hooks, or logic that deserves its own tests: Use an Observer. It keeps the model itself focused on data and relationships, not side effects.
A Practical Example: Audit Logging
Combining this with what we’ve covered, an Observer that logs every meaningful change to a model, useful for admin panels and audit trails:
class OrderObserver
{
public function created(Order $order)
{
Log::info("Order #{$order->id} created.");
}
public function updated(Order $order)
{
Log::info("Order #{$order->id} updated.");
}
public function deleted(Order $order)
{
Log::info("Order #{$order->id} deleted.");
}
}Three lines of registration, and every order change is tracked automatically, with zero changes needed in any controller.
What’s Next
We’ve now covered lifecycle hooks, the last building block before putting everything together. In Part 9, the final part, we build one real feature end to end, using everything from this entire series.
Read the previous parts:
Part 1: Getting Started with Eloquent
Part 2: Relationships I (One-to-One and One-to-Many)
Part 3: Relationships II (Many-to-Many and Polymorphic)
Part 4: Query Scopes and Mutators
Part 5: Eager Loading
Part 6: Performance Optimization
Part 7: Advanced Eloquent Patterns









