Broadcasting
Broadcasting is off by default — the plain events still fire either way. Turn it on to push changes live over Laravel broadcasting (Reverb, Pusher, Ably, …):
REALTIME_MESSAGES=trueWhen enabled, the Thread, Participant and Message models use Laravel’s BroadcastsEvents on create, update, delete and restore. A new thread is announced once its participants are in, so a private thread reaches every one of them. A soft delete — an unsend or a leave — fires both of Laravel’s trashed and deleted model events but goes out once, as trashed; a force delete goes out once, as deleted.
A thread broadcasts only its creation: renaming, archiving or deleting one is not broadcast. React to the ThreadRenamed and ThreadArchived events to push those yourself — or enable system messages, which posts a (broadcast) “renamed” message into the thread.
Channels
| Channel | Type | Carries |
|---|---|---|
messaging | public | messaging.thread.created for public threads. |
messaging.participant.{name}.{id} | private | messaging.thread.created for private and direct threads — once per participant: {id} is its key, {name} its lowercased class basename (e.g. user) and {type} its full morph type (see below). |
messaging.thread.{id} | private | Participant events (joined, read, left), message events (sent, updated, unsent, restored) and typing. |
The package registers no channel authorization callbacks — authorize the private channels in your app’s routes/channels.php, for example by checking that the user is a participant of the thread (Thread::query()->forParticipant($user)).
Channel and event names
Every channel and event name is configurable:
'broadcasting' => [
'enabled' => env('REALTIME_MESSAGES', false),
'threads' => [
'public-channel' => 'messaging',
'per-participant-channel' => 'messaging.participant.{name}.{id}', // {name}, {type} or {id}
'events' => [
'created' => 'messaging.thread.created',
],
],
'participants' => [
'channel' => 'messaging.thread.{id}',
'events' => [
'created' => 'messaging.participant.joined',
'updated' => 'messaging.participant.read',
'trashed' => 'messaging.participant.left',
'restored' => 'messaging.participant.joined',
'deleted' => 'messaging.participant.left',
],
],
'typing' => [
'event' => 'messaging.participant.typing',
],
'messages' => [
'channel' => 'messaging.thread.{id}',
'events' => [
'created' => 'messaging.message.sent',
'updated' => 'messaging.message.updated',
'trashed' => 'messaging.message.unsent',
'restored' => 'messaging.message.restored',
'deleted' => 'messaging.message.unsent',
],
],
],Per-participant channel placeholders
The per-participant channel takes three placeholders: {id} is the participant’s key; {name} its lowercased class basename (App\Models\User → user); {type} its full morph type, lowercased with \ as . (app.models.user, or the alias itself under a morph map). Without a morph map, two models sharing a basename — App\Models\User and App\Models\Admin\User — share a {name} channel and each hears the other’s thread.created announcements. Switch to {type} and authorise that channel in routes/channels.php, or register a morph map:
// config/messages.php — 'broadcasting' => [ 'threads' => [ … ] ]
'per-participant-channel' => 'messaging.participant.{type}.{id}',
// App\Models\Admin\User #5 → private-messaging.participant.app.models.admin.user.5Payloads
- Thread — id, name, is_public, everyone_can_join.
- Participant — id, participant (participateAs()), read_at, joined_at, left_at.
- Message — id, sender (participateAs()), message (null once unsent), sent_at, updated_at, deleted_at.
- Typing — thread_id, participant (participateAs()).
Typing indicators
Broadcast a live typing indicator on the thread channel — it only broadcasts (never persists), respects broadcasting.enabled, and refuses anyone who is not a current participant with a ParticipationException, whether or not broadcasting is on:
use RoundlyConsulting\Messages\Facades\Messages;
Messages::thread($thread)->typing($user); // broadcasts ParticipantTyping — never persisted
$thread->typing($user); // the same, from the modelShow your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.