Laravel Reference
What events and listeners are for
An event is just a statement that something happened — OrderShipped, UserRegistered — with no opinion on what should be done about it. A listener is the opposite: it reacts to an event without needing to know where the event came from. Splitting the two apart keeps the code that triggers something (a controller, a job, another listener) decoupled from an open-ended, growing list of side effects — sending an email, updating a stat, notifying a Slack channel — without that triggering code having to know or care about any of them.
Creating an event and listener
php artisan make:event OrderShipped
php artisan make:listener SendShipmentNotification
class OrderShipped
{
use Dispatchable, SerializesModels;
public function __construct(
public Order $order,
) {}
}
class SendShipmentNotification
{
public function handle(OrderShipped $event): void
{
$event->order->customer->notify(new ShipmentSent($event->order));
}
}
Dispatching an event
OrderShipped::dispatch($order);
// or, equivalently
event(new OrderShipped($order));
Listener discovery
Current versions of Laravel find listeners automatically by scanning the app/Listeners directory and matching each listener's handle() method to the event type it's type-hinted against — there's no separate EventServiceProvider map to keep updated for most cases. A listener only needs registering by hand for cases discovery can't infer, such as a closure-based listener or one that lives outside the conventional location.
Queued listeners
A listener can do slow work — sending mail, calling an API — without slowing down whatever dispatched the event, by implementing ShouldQueue just like a job:
class SendShipmentNotification implements ShouldQueue
{
public function handle(OrderShipped $event): void
{
$event->order->customer->notify(new ShipmentSent($event->order));
}
}
This is the main point where the two topics meet: once a listener implements ShouldQueue, it behaves exactly like any other queued job — same connection, same worker, same retry and backoff options. See the queues guide for how dispatching, workers, and failures work in full.
A queued listener triggered from inside a database transaction runs the moment it's dispatched by default — before the transaction has actually committed, which is a problem if the listener reads data the transaction just wrote (it may not see it yet, or the transaction may still roll back). Implementing ShouldQueueAfterCommit instead of ShouldQueue defers the listener until the enclosing transaction commits successfully.
Returning false from a listener's handle() method halts propagation to any remaining listeners for that event — useful for a listener that validates something and wants to prevent the rest of the chain from running, though this is uncommon enough that most event/listener pairs never need it.
When several listeners logically belong together (all reacting to a related family of events, e.g. everything to do with user activity logging), an event subscriber groups them in one class instead of several separate listener classes:
class UserEventSubscriber
{
public function handleUserLogin(Login $event): void { /* ... */ }
public function handleUserLogout(Logout $event): void { /* ... */ }
public function subscribe(Dispatcher $events): void
{
$events->listen(Login::class, [self::class, 'handleUserLogin']);
$events->listen(Logout::class, [self::class, 'handleUserLogout']);
}
}
Register it in AppServiceProvider::boot() with Event::subscribe(UserEventSubscriber::class).
Model events and observers
Eloquent models fire their own events through their lifecycle — creating, created, updating, updated, saving, deleting, deleted, and more — which can be hooked directly:
protected static function booted(): void
{
static::created(function (Post $post) {
Cache::forget('posts.count');
});
}
Once there's more than one or two of these, an Observer keeps them out of the model class itself:
php artisan make:observer PostObserver --model=Post
class PostObserver
{
public function created(Post $post): void
{
Cache::forget('posts.count');
}
public function deleting(Post $post): void
{
$post->comments()->delete();
}
}
See the Eloquent guide for more on models and their lifecycle.
Framework-built-in events
Laravel dispatches its own events for things happening under the hood, which can be listened to the same way as an app-defined one — for example Illuminate\Auth\Events\Login fires whenever a user successfully authenticates, useful for recording a last-login timestamp or an audit log entry without touching the authentication code itself.
Notifications from a listener
Sending a notification is one of the most common things a listener does — reacting to "this happened" by alerting someone about it. See the mail and notifications guide for building the notification itself.
Broadcasting events to the frontend
An event implementing ShouldBroadcast is pushed over WebSockets to any listening frontend the moment it's dispatched, in addition to (or instead of) triggering server-side listeners — the basis for live UI updates like a chat message or a live notification badge appearing without a page refresh:
class OrderShipped implements ShouldBroadcast
{
public function broadcastOn(): array
{
return [new PrivateChannel('orders.' . $this->order->id)];
}
}
This needs a broadcasting driver configured (Laravel Reverb, Pusher, or Ably) and a frontend client (Laravel Echo) subscribed to the same channel — broadcasting a notification specifically, rather than an arbitrary event, is covered as the broadcast channel on the mail and notifications guide.
Testing with Event::fake()
Event::fake();
// ... code under test that should dispatch OrderShipped ...
Event::assertDispatched(OrderShipped::class, function ($event) use ($order) {
return $event->order->id === $order->id;
});
Event::assertNotDispatched(OrderCancelled::class);
Event::fake() prevents listeners from actually running during the test, so the assertion checks that the right event was dispatched with the right data without triggering its real side effects — the same idea as Mail::fake() and Notification::fake() on the testing page.