To broadcast an event in Laravel 13, make the event class implement ShouldBroadcast, return a channel from broadcastOn(), dispatch the event, and subscribe to the channel in the browser with Laravel Echo. This guide targets Laravel 13 and PHP 8.3 or newer and uses a chat-style example.
How does Laravel event broadcasting work?
Your server dispatches a normal Laravel event. Because the event implements ShouldBroadcast, Laravel pushes it to the queue, a queue worker sends it to the broadcaster (Reverb, Pusher or Ably), and the broadcaster delivers it to every browser subscribed to that channel.
Prerequisites
- Laravel 13, PHP 8.3 or newer, Node.js and npm.
- Broadcasting installed with
php artisan install:broadcasting(see the Reverb option in the installer). - A running queue worker, or
QUEUE_CONNECTION=syncwhile testing.
Step 1: Create the event
php artisan make:event MessageSent<?php
namespace App\Events;
use App\Models\Message;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class MessageSent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct(public Message $message)
{
}
/** @return array<int, Channel> */
public function broadcastOn(): array
{
return [
new PrivateChannel('rooms.' . $this->message->room_id),
];
}
public function broadcastAs(): string
{
return 'message.sent';
}
/** @return array<string, mixed> */
public function broadcastWith(): array
{
return [
'id' => $this->message->id,
'body' => $this->message->body,
'user' => $this->message->user->name,
];
}
}Three methods matter here. broadcastOn() chooses the channels, broadcastAs() sets a short event name instead of the class name, and broadcastWith() limits the payload. Public properties are broadcast automatically if you skip broadcastWith(), so be careful not to leak data.
Step 2: Which channel type should you use?
- Channel is public. Anyone can subscribe, so use it for non-sensitive data such as a public score board.
- PrivateChannel needs the user to be authorised in
routes/channels.php. - PresenceChannel is private and also tells clients who else is subscribed, which suits online-user lists.
Step 3: Authorise the private channel
<?php
use App\Models\Room;
use Illuminate\Support\Facades\Broadcast;
Broadcast::channel('rooms.{roomId}', function ($user, int $roomId) {
return Room::whereKey($roomId)
->whereHas('members', fn ($q) => $q->whereKey($user->id))
->exists();
});Return true or false for private channels. For presence channels, return an array of user data instead.
Step 4: Dispatch the event
public function store(Request $request, Room $room)
{
$data = $request->validate(['body' => ['required', 'string', 'max:1000']]);
$message = $room->messages()->create([
'user_id' => $request->user()->id,
'body' => $data['body'],
]);
MessageSent::dispatch($message);
return response()->json($message, 201);
}If you do not want the sender to receive their own event, call broadcast(new MessageSent($message))->toOthers(). Laravel uses the socket id header that Echo sends with each request.
Step 5: Listen with Echo
window.Echo.private('rooms.' + roomId)
.listen('.message.sent', (e) => {
appendMessage(e.user, e.body);
});The leading dot in .message.sent tells Echo that you used a custom broadcastAs() name, so it should not prefix the namespace.
How do you show who is online with a presence channel?
Presence channels extend private ones. The authorisation callback returns data about the user, and Echo exposes the member list. This is how you build an online users panel.
Broadcast::channel('rooms.{roomId}.presence', function ($user, int $roomId) {
if (! $user->rooms()->whereKey($roomId)->exists()) {
return false;
}
return ['id' => $user->id, 'name' => $user->name];
});window.Echo.join('rooms.' + roomId + '.presence')
.here((users) => renderOnline(users))
.joining((user) => addOnline(user))
.leaving((user) => removeOnline(user));How do you test broadcasting without a WebSocket server?
You do not need Reverb in automated tests. Fake the event bus and assert the event was dispatched with the right data. This checks your controller and event class, while a manual check covers the socket connection.
use App\Events\MessageSent;
use Illuminate\Support\Facades\Event;
it('dispatches MessageSent when a message is posted', function () {
Event::fake([MessageSent::class]);
$user = User::factory()->create();
$room = Room::factory()->hasAttached($user, relationship: 'members')->create();
$this->actingAs($user)
->postJson("/rooms/{$room->id}/messages", ['body' => 'Hello'])
->assertCreated();
Event::assertDispatched(MessageSent::class);
});For the channel callback itself, you can also assert authorisation by calling the /broadcasting/auth endpoint with a fake socket id and checking the response status.
When should you skip broadcasting?
Broadcasting suits chat, live dashboards and notifications. If a page only needs fresh data every few seconds and has few viewers, simple polling is cheaper to run and easier to debug. Choose WebSockets when latency and server load matter more than simplicity.
Common mistakes
- Forgetting the leading dot when using
broadcastAs(). The listener then never fires. - Not running a queue worker. The event is queued and sits there.
- Broadcasting whole Eloquent models. Always shape the payload with
broadcastWith(). - Subscribing to a private channel from a guest page. The auth request fails with 401 or 403.
- Using
ShouldBroadcastNoweverywhere. It skips the queue and slows the HTTP response, so keep it for small cases.
How do you verify it works?
- Start the processes:
php artisan reverb:start --debug,php artisan queue:workandnpm run dev. - Open the page in two browsers logged in as two room members.
- Post a message in one browser. The other should show it without a refresh.
- In the debug output of Reverb you should see the subscription and the event being delivered.
FAQ
What is the difference between ShouldBroadcast and ShouldBroadcastNow?
ShouldBroadcast queues the broadcast. ShouldBroadcastNow sends it immediately during the request.
Do I need Reverb to broadcast events?
No. Pusher, Ably and other supported drivers use the same event classes. Only the connection config changes.
How do I broadcast only when a condition is true?
Add a broadcastWhen() method that returns a boolean.
Can the browser send events too?
Browsers can send client events called whispers on private and presence channels, which is useful for typing indicators.



