Laravel’s events let one action trigger several reactions, without the code that does the action knowing about them. After a purchase, one event can send the email, log the activity and queue a sync, each in its own listener. The controller stays short, and you can add a reaction without touching it.
Why use events?
Event-driven design:
- Decouples logic: the core action stays short
- Improves testability: listeners can be tested in isolation
- Moves slow work off the request: listeners can run synchronously or via queues
- Encourages single responsibility: each listener handles one job
An event and a listener
Generate both:
php artisan make:event OrderPlaced
php artisan make:listener SendOrderConfirmation --event=OrderPlaced
Event
<?php
namespace App\Events;
use App\Models\Order;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class OrderPlaced
{
use Dispatchable, SerializesModels;
public function __construct(public Order $order)
{
}
}
Listener
class SendOrderConfirmation
{
public function handle(OrderPlaced $event)
{
Mail::to($event->order->user)->send(new OrderConfirmation($event->order));
}
}
In Laravel 11 and later, event discovery registers this for you. Laravel scans app/Listeners and links each listener to the event type-hinted in its handle() method, so there’s nothing else to wire up.
To register a listener by hand, for example one that lives outside app/Listeners, use Event::listen in the boot() method of AppServiceProvider:
use Illuminate\Support\Facades\Event;
Event::listen(OrderPlaced::class, SendOrderConfirmation::class);
Older applications list events and listeners in the $listen array of EventServiceProvider instead.
Dispatching events
Anywhere in your app:
OrderPlaced::dispatch($order);
Or use the helper:
event(new OrderPlaced($order));
Queued listeners
Implement ShouldQueue and the listener runs on a queue worker instead of during the request:
use Illuminate\Contracts\Queue\ShouldQueue;
class QueueAnalyticsEvent implements ShouldQueue
{
public function handle(OrderPlaced $event)
{
Analytics::track('order_placed', [
'order_id' => $event->order->id,
'user_id' => $event->order->user_id,
]);
}
}
Queue config lives in config/queue.php. On Redis queues, watch the workers with Horizon.
Grouping listeners in a subscriber
A subscriber holds several listeners in one class:
class UserEventSubscriber
{
public function onLogin($event)
{
// log or notify
}
public function onLogout($event)
{
// track session end
}
public function subscribe(Dispatcher $events)
{
$events->listen(Login::class, [self::class, 'onLogin']);
$events->listen(Logout::class, [self::class, 'onLogout']);
}
}
Register it in the boot() method of AppServiceProvider:
Event::subscribe(UserEventSubscriber::class);
Broadcasting events
For live updates in the browser, keep broadcasting separate from your domain events, and broadcast only what the page needs.
A broadcast event implements ShouldBroadcast. Without it, Laravel never sends it to the browser. Give it its own class, and send only the fields the UI needs:
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class OrderStatusUpdated implements ShouldBroadcast
{
public function __construct(public int $orderId, public string $status)
{
}
public function broadcastOn(): PrivateChannel
{
return new PrivateChannel('orders.'.$this->orderId);
}
}
Run php artisan install:broadcasting to set up broadcasting and Laravel Echo, then authorise the private channel in routes/channels.php so users only hear about their own orders. Listen on the frontend:
Echo.private(`orders.${orderId}`)
.listen('OrderStatusUpdated', (e) => {
console.log('Order status changed', e.status);
});
Habits that help
- Use event names that reflect domain actions such as
OrderShippedorInvoiceGenerated - Give each listener one job
- Queue heavy work like notifications, analytics, or third-party calls
- Test events and listeners in isolation
- Monitor your queues with Horizon or Telescope
A real example
On a Laravel trading platform, a queue-based engine matched orders with rollback-safe database transactions, and Redis broadcasting through Laravel Echo pushed live prices and order updates to every user.
Related posts
Events are how I keep the follow-on work of an action, like an email or a sync, out of its controller when I build a Laravel application.
Published , updated .
Part 4 of Laravel Mastery.