Laravel Events & Listeners Explained - Complete Guide With Real Example
Laravel: Up & Running
What sets Laravel apart from other PHP web frameworks? Speed and simplicity, for starters. This rapid application development framework and its ecosystem of tools let you quickly build new sites and applications with clean, readable code.
As your Laravel application grows, you will start to notice a pattern โ a single action triggers multiple side effects. A user registers and you need to send a welcome email, create a default profile, notify the admin, and log the activity. If you put all of that logic inside your controller or model, it becomes a mess very quickly. Laravel Events and Listeners exist to solve exactly this problem โ keeping your code clean, decoupled, and easy to extend without touching existing code.
In this guide we will cover how Laravel's event system works, how to create events and listeners, how to queue listeners for performance, and walk through a complete real-world example from start to finish.
What Are Events and Listeners?
Events and Listeners implement the Observer pattern in Laravel. An Event is a class that represents something that happened in your application โ a user registered, an order was placed, a payment failed. A Listener is a class that responds to that event and handles a specific piece of logic โ send an email, update a record, notify a third-party service.
The key benefit is decoupling. The part of your code that fires the event does not need to know anything about what happens as a result. It just says "this happened" and walks away. The listeners take care of the rest. This means you can add new behaviour to an event by creating a new listener โ without touching the original code that fires the event.
// Without events โ tightly coupled, hard to maintain
public function register(Request $request)
{
$user = User::create($request->validated());
// All of this lives in the controller โ grows forever
Mail::to($user)->send(new WelcomeEmail($user));
Profile::create(['user_id' => $user->id]);
AdminNotification::send($user);
ActivityLog::record('user_registered', $user->id);
Slack::notify("New user: {$user->name}");
return redirect('/dashboard');
}
// With events โ clean, decoupled, extensible
public function register(Request $request)
{
$user = User::create($request->validated());
event(new UserRegistered($user)); // fire and forget
return redirect('/dashboard');
}
The Real-World Example We Will Build
We will build the event system for a blog application. When a user publishes a post, we want to:
- Send a notification email to all subscribers
- Update the user's post count in their profile
- Log the publish activity
- Send a ping to search engines (optional)
All of this happens when one event fires โ PostPublished.
We will build the whole thing step by step.
Step 1 โ Register Events and Listeners in EventServiceProvider
The first step is to tell Laravel which listeners should respond to which
events. This is done in app/Providers/EventServiceProvider.php:
// app/Providers/EventServiceProvider.php
namespace App\Providers;
use App\Events\UserRegistered;
use App\Events\PostPublished;
use App\Listeners\SendWelcomeEmail;
use App\Listeners\CreateUserProfile;
use App\Listeners\NotifySubscribers;
use App\Listeners\UpdatePostCount;
use App\Listeners\LogPostActivity;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
protected $listen = [
// UserRegistered event โ multiple listeners
UserRegistered::class => [
SendWelcomeEmail::class,
CreateUserProfile::class,
],
// PostPublished event โ multiple listeners
PostPublished::class => [
NotifySubscribers::class, // queued โ sends emails
UpdatePostCount::class, // fast โ updates a counter
LogPostActivity::class, // fast โ writes to log
],
];
}
Step 2 โ Generate Event and Listener Files
Once you have registered your events and listeners, Laravel can generate all the boilerplate files with a single command:
// Generate all registered events and listeners at once
php artisan event:generate
// Or generate individually
php artisan make:event PostPublished
php artisan make:listener NotifySubscribers --event=PostPublished
php artisan make:listener UpdatePostCount --event=PostPublished
php artisan make:listener LogPostActivity --event=PostPublished
Step 3 โ Create the Event Class
An event class is a simple data container โ it holds the data that
listeners will need. For our PostPublished event, the
listeners need access to the post that was published:
// app/Events/PostPublished.php
namespace App\Events;
use App\Models\Post;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class PostPublished
{
use Dispatchable, InteractsWithSockets, SerializesModels;
// The post that was published โ available to all listeners
public Post $post;
public function __construct(Post $post)
{
$this->post = $post;
}
}
// app/Events/UserRegistered.php
namespace App\Events;
use App\Models\User;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class UserRegistered
{
use Dispatchable, SerializesModels;
public User $user;
public function __construct(User $user)
{
$this->user = $user;
}
}
Tip: The SerializesModels trait is
important when listeners are queued. It serializes Eloquent models
properly so they can be stored in the queue and re-fetched from the
database when the queued listener runs โ rather than trying to
serialize the entire model object.
Step 4 โ Create the Listeners
Each listener has a handle() method that receives the event
and does one specific thing. Keep each listener focused on a single
responsibility:
Listener 1 โ Notify Subscribers (Queued)
// app/Listeners/NotifySubscribers.php
namespace App\Listeners;
use App\Events\PostPublished;
use App\Mail\NewPostNotification;
use App\Models\Subscriber;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Mail;
class NotifySubscribers implements ShouldQueue
{
use InteractsWithQueue;
// Queue configuration
public string $queue = 'notifications';
public int $delay = 0;
public int $tries = 3; // retry 3 times on failure
public function handle(PostPublished $event): void
{
$post = $event->post;
// Get all active subscribers
$subscribers = Subscriber::where('is_active', true)->get();
foreach ($subscribers as $subscriber) {
Mail::to($subscriber->email)
->send(new NewPostNotification($post, $subscriber));
}
}
// Called if all retries fail
public function failed(PostPublished $event, \Throwable $exception): void
{
\Log::error("Failed to notify subscribers for post: {$event->post->id}", [
'error' => $exception->getMessage()
]);
}
}
Listener 2 โ Update Post Count
// app/Listeners/UpdatePostCount.php
namespace App\Listeners;
use App\Events\PostPublished;
class UpdatePostCount
{
// No ShouldQueue โ this is fast, run synchronously
public function handle(PostPublished $event): void
{
$post = $event->post;
// Increment the author's published post count
$post->user()->increment('published_posts_count');
}
}
Listener 3 โ Log Post Activity
// app/Listeners/LogPostActivity.php
namespace App\Listeners;
use App\Events\PostPublished;
use App\Models\ActivityLog;
class LogPostActivity
{
public function handle(PostPublished $event): void
{
$post = $event->post;
ActivityLog::create([
'user_id' => $post->user_id,
'action' => 'post_published',
'description' => "Published post: {$post->title}",
'meta' => json_encode([
'post_id' => $post->id,
'post_slug' => $post->slug,
]),
]);
}
}
Listener 4 โ Send Welcome Email
// app/Listeners/SendWelcomeEmail.php
namespace App\Listeners;
use App\Events\UserRegistered;
use App\Mail\WelcomeEmail;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Mail;
class SendWelcomeEmail implements ShouldQueue
{
public string $queue = 'emails';
public function handle(UserRegistered $event): void
{
Mail::to($event->user->email)
->send(new WelcomeEmail($event->user));
}
}
Step 5 โ Fire the Event
Now that everything is wired up, firing the event is a single line anywhere in your application:
// In your PostController
namespace App\Http\Controllers;
use App\Events\PostPublished;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function publish(Request $request, Post $post)
{
// Authorization check
$this->authorize('publish', $post);
// Update the post status
$post->update([
'published_at' => now(),
'is_published' => true,
]);
// Fire the event โ all listeners handle the rest
event(new PostPublished($post));
// Or using the static dispatch method (same thing)
// PostPublished::dispatch($post);
return redirect()
->route('posts.show', $post->slug)
->with('success', 'Your post has been published!');
}
}
// You can also fire events from models using $dispatchesEvents
// app/Models/Post.php
class Post extends Model
{
// Automatically fire events when model actions happen
protected $dispatchesEvents = [
'created' => PostCreated::class,
'updated' => PostUpdated::class,
'deleted' => PostDeleted::class,
];
}
// Now whenever a Post is saved โ the event fires automatically
// No need to call event() manually in your controller
Step 6 โ Queued Listeners
Any listener that implements ShouldQueue will be pushed to
your queue instead of running synchronously. This is essential for
anything slow โ sending emails, calling external APIs, heavy database
operations. The HTTP response returns immediately and the work happens
in the background.
// Make sure your queue worker is running
php artisan queue:work
// Or for production with Supervisor (recommended)
php artisan queue:work --queue=notifications,emails,default --tries=3
// Check failed jobs
php artisan queue:failed
// Retry failed jobs
php artisan queue:retry all
// Your .env queue configuration
QUEUE_CONNECTION=database // or redis (recommended for production)
// Create the jobs table if using database driver
php artisan queue:table
php artisan migrate
Event Subscribers โ Group Related Listeners in One Class
If you have many listeners that all relate to the same domain โ like all post-related events โ you can group them into a single subscriber class instead of creating a separate file for each listener:
// app/Listeners/PostEventSubscriber.php
namespace App\Listeners;
use App\Events\PostPublished;
use App\Events\PostDeleted;
use App\Events\PostViewed;
use Illuminate\Events\Dispatcher;
class PostEventSubscriber
{
// Handle PostPublished event
public function handlePostPublished(PostPublished $event): void
{
\Log::info("Post published: {$event->post->title}");
$event->post->user()->increment('published_posts_count');
}
// Handle PostDeleted event
public function handlePostDeleted(PostDeleted $event): void
{
\Log::info("Post deleted: {$event->post->id}");
$event->post->user()->decrement('published_posts_count');
}
// Handle PostViewed event
public function handlePostViewed(PostViewed $event): void
{
$event->post->increment('view_count');
}
// Register all listeners in this subscriber
public function subscribe(Dispatcher $events): void
{
$events->listen(PostPublished::class, [self::class, 'handlePostPublished']);
$events->listen(PostDeleted::class, [self::class, 'handlePostDeleted']);
$events->listen(PostViewed::class, [self::class, 'handlePostViewed']);
}
}
// Register the subscriber in EventServiceProvider
protected $subscribe = [
PostEventSubscriber::class,
];
Model Events โ Built-in Eloquent Events
Eloquent models fire their own events automatically during the lifecycle of a record. You can hook into these without creating custom event classes:
// Available model events:
// creating, created, updating, updated, saving, saved
// deleting, deleted, restoring, restored, retrieved
// Method 1: observing in the model using boot()
class Post extends Model
{
protected static function boot(): void
{
parent::boot();
// Before creating โ generate slug automatically
static::creating(function (Post $post) {
$post->slug = Str::slug($post->title);
});
// After creating
static::created(function (Post $post) {
\Log::info("New post created: {$post->id}");
});
// Before deleting โ clean up related data
static::deleting(function (Post $post) {
$post->comments()->delete();
$post->tags()->detach();
});
}
}
// Method 2: Observer class (cleaner for many hooks)
php artisan make:observer PostObserver --model=Post
// app/Observers/PostObserver.php
class PostObserver
{
public function creating(Post $post): void
{
$post->slug = Str::slug($post->title);
}
public function created(Post $post): void
{
ActivityLog::record('post_created', $post->id);
}
public function updated(Post $post): void
{
if ($post->wasChanged('is_published') && $post->is_published) {
PostPublished::dispatch($post); // fire custom event
}
}
public function deleted(Post $post): void
{
$post->comments()->delete();
$post->tags()->detach();
}
}
// Register the observer in AppServiceProvider or a model
// In AppServiceProvider::boot()
Post::observe(PostObserver::class);
Testing Events and Listeners
// Laravel makes it easy to test events without actually firing listeners
use App\Events\PostPublished;
use App\Listeners\NotifySubscribers;
use Illuminate\Support\Facades\Event;
// Test that an event was fired
public function test_post_published_event_is_fired()
{
Event::fake();
$post = Post::factory()->create();
$this->actingAs($post->user)
->post("/posts/{$post->id}/publish");
Event::assertDispatched(PostPublished::class, function ($event) use ($post) {
return $event->post->id === $post->id;
});
}
// Test that an event was NOT fired
Event::assertNotDispatched(PostPublished::class);
// Test a specific listener handles the event correctly
public function test_notify_subscribers_listener()
{
$post = Post::factory()->published()->create();
$subscriber = Subscriber::factory()->create();
Mail::fake();
$listener = new NotifySubscribers();
$listener->handle(new PostPublished($post));
Mail::assertSent(NewPostNotification::class, function ($mail) use ($subscriber) {
return $mail->hasTo($subscriber->email);
});
}
Events vs Jobs โ When to Use Which
| Events + Listeners | Jobs | |
|---|---|---|
| Best for | One action triggers multiple things | One specific task dispatched to queue |
| Listeners | Multiple per event | One handler |
| Coupling | Loosely coupled โ easy to extend | Directly dispatched |
| Queue support | Yes โ implement ShouldQueue | Yes โ built in |
| Example use case | User registered โ email + profile + log | Generate a PDF report |
Common Mistakes to Avoid
1. Not queuing slow listeners
// โ Sending email synchronously โ slows down the HTTP response
class SendWelcomeEmail
{
public function handle(UserRegistered $event): void
{
Mail::to($event->user)->send(new WelcomeEmail($event->user)); // blocks
}
}
// โ
Queue the listener โ response returns immediately
class SendWelcomeEmail implements ShouldQueue
{
public function handle(UserRegistered $event): void
{
Mail::to($event->user)->send(new WelcomeEmail($event->user));
}
}
2. Putting too much logic in the event class
// โ Event classes should be data containers only
class PostPublished
{
public function __construct(public Post $post)
{
// Don't put business logic here
$this->post->update(['published_at' => now()]); // โ wrong place
}
}
// โ
Event just holds the data
class PostPublished
{
public function __construct(public Post $post) {}
}
3. Forgetting to register events in EventServiceProvider
// If you create event and listener files manually
// without running event:generate or registering in $listen
// the listener will never fire โ no error thrown, just silent failure
// Always verify your EventServiceProvider $listen array
// Or use event:list to see all registered events
php artisan event:list
Final Thought
Laravel Events and Listeners are one of the most elegant patterns in the framework. Once you start using them, you will find yourself reaching for them naturally whenever an action in your application needs to trigger multiple downstream effects. They keep your controllers thin, your models focused, and your codebase easy to extend without touching what already works.
The real power shows when your application grows. Need to add a new behaviour when a post is published? Add a new listener. No changes to the controller, no changes to existing listeners, no risk of breaking what already works. Just a new class that handles one thing.
If you are building a Laravel blog application or any data-driven app, also check out our guide on Laravel Eloquent Relationships Explained with Real-World Examples - understanding relationships is the foundation of building the kind of data models that make events and listeners most useful.
Have a specific use case you are trying to implement with events in your Laravel project? Drop a message on our contact page and we will help you figure out the right approach.
Laravel: Up & Running
What sets Laravel apart from other PHP web frameworks? Speed and simplicity, for starters. This rapid application development framework and its ecosystem of tools let you quickly build new sites and applications with clean, readable code.
๐ You Might Also Like
- โ Best Google AdSense Alternative 2026 - Monetag Review for Publishers miscellaneous
- โ Top Java Interview Questions for 6+ Years Experience (2026) java
- โ How to Start Freelancing as a Web Developer in 2026 (Complete Beginner's Guide) miscellaneous
- โ HTTP QUERY Method (RFC 10008) โ The New HTTP Method Every Developer Should Know miscellaneous
- โ MyLync - Best Free Linktree Alternative for Developers, Creators & Businesses miscellaneous