Building a "Who's Online" Presence Tracker with Laravel Reverb
Public broadcast channels tell you an event happened. Private channels tell you a specific user can listen. Presence channels do something different: they track exactly who is currently connected, and notify everyone else the moment someone joins or leaves. This tutorial builds a live "who's online" list for a shared document view using Laravel Reverb.
Step 1: Install and configure Reverb
php artisan install:broadcasting
This scaffolds Reverb, sets your BROADCAST_CONNECTION to reverb in .env, and wires up the frontend Echo configuration for you.
Step 2: Define the presence channel
Presence channels use the same channels.php file as private channels, but the authorization callback returns an array of data about the user rather than a plain boolean — that returned array is what makes someone "present."
use App\Models\Document;
use Illuminate\Support\Facades\Broadcast;
Broadcast::channel('document.{documentId}', function ($user, $documentId) {
$document = Document::findOrFail($documentId);
if (! $user->canView($document)) {
return null;
}
return [
'id' => $user->id,
'name' => $user->name,
'avatar' => $user->avatar_url,
];
});
Returning null denies access, same as returning false would on a private channel. Returning an array both authorizes the connection and defines what data other members of the channel will see about this person.
Step 3: Join the presence channel from the frontend
import Echo from 'laravel-echo';
const channel = window.Echo.join(`document.${documentId}`)
.here((users) => {
// Everyone currently present, on initial join
setOnlineUsers(users);
})
.joining((user) => {
setOnlineUsers((current) => [...current, user]);
})
.leaving((user) => {
setOnlineUsers((current) => current.filter((u) => u.id !== user.id));
})
.error((error) => {
console.error('Presence channel error', error);
});
.here() fires once, immediately, with the full roster of everyone already connected. .joining() and .leaving() fire afterward as people connect and disconnect — Reverb handles the bookkeeping of tracking connections and notifying the channel automatically, you never manually broadcast a "user joined" event yourself.
Step 4: Broadcasting activity within the channel
Presence gives you who's online; you can layer ordinary events onto the same channel for activity like cursor position or "is typing" indicators.
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class UserIsTypingInDocument implements ShouldBroadcast
{
use InteractsWithSockets;
public function __construct(
public int $documentId,
public int $userId,
) {}
public function broadcastOn(): Channel
{
return new PresenceChannel("document.{$this->documentId}");
}
}
Listen for it the same way as any other Echo event, on the same channel instance you already joined:
channel.listen('UserIsTypingInDocument', (event) => {
showTypingIndicator(event.userId);
});
Step 5: Rendering the roster
<div class="flex -space-x-2">
@foreach($onlineUsers as $user)
<img src="{{ $user['avatar'] }}"
title="{{ $user['name'] }}"
class="w-8 h-8 rounded-full border-2 border-white">
@endforeach
</div>
(This part is rendered client-side from the onlineUsers state built up by .here()/.joining()/.leaving() — the Blade above is illustrative of the shape of the data, not a server-rendered list, since presence is inherently a live client-side concept.)
A note on scaling
Presence channels require Reverb (or Pusher) to track connection state per channel, which means every server in a horizontally scaled Reverb deployment needs to share that state — Reverb's Redis scaling mode handles this for you when you run more than one Reverb instance behind a load balancer. For a single-server deployment, the default setup here works with no extra configuration.
Presence channels turn "is anyone else looking at this right now" from a polling endpoint you'd have to build and rate-limit yourself into something Reverb tracks for free.