Muhammad Yasir Amin

Loading websiteโ€ฆ

More from Muhammad Yasir Amin

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Subscribe on YouTube

Laravel 13 Event Broadcasting Tutorial with Private Channels and Echo

๐Ÿ› ๏ธ Laravel Tutorials5 min read

Laravel 13 Event Broadcasting Tutorial with Private Channels and Echo

Muhammad Yasir Amin

Muhammad Yasir Amin

Senior Full Stack Engineerโ€ขNov 7, 2026
Laravel 13 Event Broadcasting Tutorial with Private Channels and Echo

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=sync while testing.

Step 1: Create the event

Code
php artisan make:event MessageSent
JavaScript
<?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

JavaScript
<?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

JavaScript
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

JavaScript
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.

JavaScript
Broadcast::channel('rooms.{roomId}.presence', function ($user, int $roomId) {
    if (! $user->rooms()->whereKey($roomId)->exists()) {
        return false;
    }

    return ['id' => $user->id, 'name' => $user->name];
});
JavaScript
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.

JavaScript
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 ShouldBroadcastNow everywhere. It skips the queue and slows the HTTP response, so keep it for small cases.

How do you verify it works?

  1. Start the processes: php artisan reverb:start --debug, php artisan queue:work and npm run dev.
  2. Open the page in two browsers logged in as two room members.
  3. Post a message in one browser. The other should show it without a refresh.
  4. 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.

References

Article Index & Topic Tags
LaravelBroadcastingEventsWebsockets

YouTube Channel

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Muhammad Yasir Amin
Available for Contracts
Author & Solutions Architect

Senior Full Stack Engineer

Independent Senior Full Stack Engineer working on Laravel, React and Next.js applications, APIs and delivery workflows.

Laravel & PHP ApplicationsReact & Next.js InterfacesREST APIs & IntegrationsDatabase Queries & DebuggingDocker Development WorkflowsClear Scope & Handover
Discuss Your Next Step

What Does Your Application Need Next?

Share your current system, the problem you want to solve and what a useful result looks like. We can identify the right starting point and agree on a practical scope.

Technical Audit

Find the Next Useful Fix

Application Development

Scoped, Reviewable Milestones

Ongoing Support

Agreed Capacity and Priorities

Share your goals, current stack and requirements before the call.