Laravel
WebSockets & Broadcasting
Laravel Broadcasting lets you broadcast server-side events over WebSockets to your frontend in real time, using drivers like Pusher, Ably, or the self-hosted Laravel WebSockets package.
Broadcasting Config: Set the broadcast driver in
.env and configure credentials. The pusher driver is the most common choice..env
ENV
BROADCAST_DRIVER=pusher
PUSHER_APP_ID=your-app-id
PUSHER_APP_KEY=your-app-key
PUSHER_APP_SECRET=your-app-secret
PUSHER_APP_CLUSTER=mt1
# For self-hosted laravel-websockets package
PUSHER_HOST=127.0.0.1
PUSHER_PORT=6001
PUSHER_SCHEME=http
Enable Broadcasting: Uncomment the
BroadcastServiceProvider in config/app.php and install the Pusher PHP SDK.terminal
BASH
composer require pusher/pusher-php-server
# If using laravel-websockets (self-hosted alternative)
composer require beyondcode/laravel-websockets
php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="migrations"
php artisan migrate
php artisan websockets:serve
Creating a Broadcast Event: Generate an event class and implement
ShouldBroadcast. Define the channel and data payload in broadcastOn() and broadcastWith().terminal
BASH
php artisan make:event OrderShipped
app/Events/OrderShipped.php
PHP
<?php
namespace App\Events;
use App\Models\Order;
use Illuminate\Broadcasting\Channel;
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) {}
// Channel to broadcast on
public function broadcastOn(): array
{
return [
new PrivateChannel("orders.{$this->order->user_id}"),
];
}
// Data sent to the frontend
public function broadcastWith(): array
{
return [
'order_id' => $this->order->id,
'status' => $this->order->status,
'updated_at' => $this->order->updated_at->toDateTimeString(),
];
}
// Custom event name (default: class name)
public function broadcastAs(): string
{
return 'order.shipped';
}
}
Firing the Event: Broadcast an event from anywhere in your application — controllers, jobs, listeners, etc.
app/Http/Controllers/OrderController.php
PHP
use App\Events\OrderShipped;
public function ship(Order $order): JsonResponse
{
$order->update(['status' => 'shipped']);
// Fire the broadcast event
broadcast(new OrderShipped($order));
// Or: only broadcast to others (exclude current request)
broadcast(new OrderShipped($order))->toOthers();
return response()->json(['message' => 'Order shipped']);
}
Channel Routes: Define channel authorization in
routes/channels.php. Private and presence channels require user authorization callbacks.routes/channels.php
PHP
<?php
use Illuminate\Support\Facades\Broadcast;
// Public channel — no auth needed (anyone can subscribe)
Broadcast::channel('announcements', function () {
return true;
});
// Private channel — return true/false to allow/deny
Broadcast::channel('orders.{userId}', function ($user, $userId) {
return (int) $user->id === (int) $userId;
});
// Presence channel — return array of user info to allow
Broadcast::channel('chat.{roomId}', function ($user, $roomId) {
if ($user->canJoinRoom($roomId)) {
return ['id' => $user->id, 'name' => $user->name];
}
});
Pusher Setup (config): Ensure
config/broadcasting.php has the Pusher options correctly set, including useTLS for production.config/broadcasting.php (pusher section)
PHP
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'cluster' => env('PUSHER_APP_CLUSTER'),
'useTLS' => true,
// Self-hosted laravel-websockets overrides:
'host' => env('PUSHER_HOST', '127.0.0.1'),
'port' => env('PUSHER_PORT', 6001),
'scheme' => env('PUSHER_SCHEME', 'http'),
'encrypted' => true,
'curl_options' => [
CURLOPT_SSL_VERIFYHOST => 0,
CURLOPT_SSL_VERIFYPEER => 0,
],
],
],
Install Laravel Echo & Pusher JS: On the frontend, install Echo and the Pusher JS client, then configure Echo in your JavaScript bootstrap file.
terminal – npm
BASH
npm install --save-dev laravel-echo pusher-js
resources/js/bootstrap.js
JS
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
window.Echo = new Echo({
broadcaster: 'pusher',
key: import.meta.env.VITE_PUSHER_APP_KEY,
cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
forceTLS: true,
// For self-hosted laravel-websockets:
// wsHost: import.meta.env.VITE_PUSHER_HOST,
// wsPort: import.meta.env.VITE_PUSHER_PORT,
// wssPort: import.meta.env.VITE_PUSHER_PORT,
// forceTLS: false,
// enabledTransports: ['ws', 'wss'],
});
Listening to Events on Frontend: Use
Echo.channel() for public, .private() for private, and .join() for presence channels.resources/js/app.js
JS
// Listen on a public channel
window.Echo.channel('announcements')
.listen('.announcement.created', (event) => {
console.log('New announcement:', event);
});
// Listen on a private channel (requires auth)
window.Echo.private(`orders.${userId}`)
.listen('.order.shipped', (event) => {
console.log('Order shipped:', event.order_id, event.status);
showNotification(`Your order #${event.order_id} has been shipped!`);
});
// Listen on a presence channel (see who is online)
window.Echo.join(`chat.${roomId}`)
.here((users) => {
console.log('Users in channel:', users);
})
.joining((user) => {
console.log(user.name, 'joined');
})
.leaving((user) => {
console.log(user.name, 'left');
})
.listen('.MessageSent', (event) => {
console.log('Message:', event.message);
});
// Stop listening / leave channel
window.Echo.leave(`orders.${userId}`);