Broadcasting & Real-Time
Some updates shouldn't wait for the user to reload the page — an order moving to "shipped," a chat message arriving, a long-running job finishing. The naive fix is polling: have the browser ask "anything new?" every few seconds. Broadcasting is Laravel's alternative — instead of the browser repeatedly asking, the server pushes the update out the instant it happens, over a persistent WebSocket connection.
Why Broadcasting Instead of Polling?
| Polling | Broadcasting | |
|---|---|---|
| How it works | Browser repeatedly asks the server "anything new?" on a timer | Server pushes the update the moment it happens |
| Latency | Up to one full polling interval late | Near-instant |
| Server load | Constant requests, even when nothing changed | One message, sent only when something actually happens |
The Architecture, End to End
Broadcasting always involves the same three pieces working together, in this order:
- Your Laravel application dispatches an event (the same events covered in Events & Listeners) that's marked as broadcastable.
- A broadcasting driver — a hosted service like Pusher, or a self-hosted WebSocket server like Laravel Reverb or Soketi — receives that event from your server and fans it out.
- Every browser currently subscribed to the relevant channel, holding open a persistent WebSocket connection, receives the event instantly and reacts to it with JavaScript — no page reload, no polling.
Making an Event Broadcastable
An ordinary Laravel event stays entirely server-side. To let it reach the browser, implement the
ShouldBroadcast interface and add a broadcastOn() method describing which
channel it should be sent over:
<?php
// app/Events/OrderShipped.php
namespace App\Events;
use App\Models\Order;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class OrderShipped implements ShouldBroadcast
{
use Dispatchable, SerializesModels;
public function __construct(public Order $order)
{
}
public function broadcastOn()
{
return new PrivateChannel('orders.'.$this->order->id);
}
}
Dispatching it is identical to any other event — Laravel notices ShouldBroadcast and
automatically routes it to your configured broadcasting driver in addition to firing any normal
listeners:
<?php
event(new OrderShipped($order));
Public, Private, and Presence Channels
Not every broadcast should be visible to every visitor, so channels come in three flavors:
| Channel type | Who can listen | Example |
|---|---|---|
Public (Channel) | Anyone — no authorization check at all | A site-wide "new article published" ticker |
Private (PrivateChannel) | Only authenticated users your app explicitly authorizes | A specific customer's own order-tracking updates |
Presence (PresenceChannel) | Same authorization as private, plus Laravel tracks exactly who is currently subscribed | A live chat room showing "who's online right now" |
Private and presence channels need an authorization rule so Laravel knows who's allowed to
subscribe, defined in routes/channels.php:
<?php
// routes/channels.php
use App\Models\Order;
Broadcast::channel('orders.{orderId}', function ($user, $orderId) {
return $user->id === Order::findOrFail($orderId)->user_id;
});
Only when this callback returns true does Laravel let that specific user's browser
subscribe to orders.42 — anyone else's subscription attempt is rejected.
Laravel Echo: Listening on the Frontend
Laravel Echo is the JavaScript library that pairs with the backend pieces above — it opens and manages the WebSocket connection and gives you a clean, event-driven API for subscribing to channels in the browser:
// resources/js/app.js (conceptual — full Echo setup is its own topic)
Echo.private(`orders.${orderId}`)
.listen('OrderShipped', (event) => {
console.log(event.order);
// update the page — e.g. change a status badge to "Shipped"
});
For a public channel the call is simply Echo.channel('orders').listen('OrderShipped', ...)
— no authorization involved. Presence channels use Echo.join() instead, which adds
.here(), .joining(), and .leaving() callbacks for tracking who's
currently connected.
A Realistic Use Case: Live Order Tracking
Put together, this is exactly how a customer's order-tracking page can update itself with zero
polling: the customer's browser subscribes to their own private orders.{id} channel
when the page loads; sometime later, a warehouse process marks the order shipped and dispatches the
OrderShipped event; the broadcasting driver delivers it over the open WebSocket
connection; Echo's .listen() callback fires in the browser and swaps the status badge
from "Processing" to "Shipped" — all without the customer ever refreshing the page.