NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Messages for Laravel

Preparing your models

Any model that takes part in messaging — a sender or a participant — must implement RoundlyConsulting\Messages\Interfaces\ParticipatesInMessaging. Add the HasMessaging trait to get the ergonomic API:

use Illuminate\Database\Eloquent\Model;
use RoundlyConsulting\Messages\Concerns\HasMessaging;
use RoundlyConsulting\Messages\Interfaces\ParticipatesInMessaging;

class User extends Model implements ParticipatesInMessaging
{
    use HasMessaging;

    public function participateAs(): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
        ];
    }
}

What participateAs() is for

participateAs() returns the public shape of a participant. Keep it to fields that are safe to show other people in the conversation:

  • Broadcast payloads — the sender of a message, the participant on join/read/leave, and the typist on a typing signal.
  • JSON resources — MessageResource.sender and ParticipantResource.participant.
  • System messages — joins and leaves store it under meta.participant.

Broadcasting a model that does not implement the interface throws ParticipationException::interfaceImplementationRequired().

Any model can participate

Participation is polymorphic, so different model types — a User and a Company, a customer and a support team — can share the same conversation:

use Illuminate\Database\Eloquent\Model;
use RoundlyConsulting\Messages\Concerns\HasMessaging;
use RoundlyConsulting\Messages\Interfaces\ParticipatesInMessaging;

class Company extends Model implements ParticipatesInMessaging
{
    use HasMessaging;

    public function participateAs(): array
    {
        return ['id' => $this->id, 'name' => $this->name];
    }
}

// A User and a Company share one conversation
$thread = $customer->startConversationWith($company, name: 'Order A-1042');
$company->sendMessageTo($thread, 'Your order ships tomorrow.');

They share the sender_id and participant_id columns, so every participating model must use the same primary-key type — set key_type to match it (see Key types).

The HasMessaging API

Everything the trait adds, at a glance. It is shorthand over the same MessagesManager the Messages facade uses, so every write runs the same action and Messages::fake() records it like a facade call:

$user->participations();                          // MorphMany<Participant>
$user->threads();                                 // Collection<Thread>, newest activity first
$user->conversations();                           // alias of threads()
$user->startConversationWith([$bob], name: 'Q3'); // Thread — $user becomes the owner
$user->conversationWith($bob);                    // Thread — find-or-create the DM
$user->sendMessageTo($thread, 'Hello');           // Message
$user->joinThread($thread);                       // Participant — self-join, only on threads open to everyone
$user->unreadThreads();                           // Collection<Thread> with unread messages
$user->unreadCount();                             // int — across every thread
$user->unreadCount($thread);                      // int — within one thread
$user->markThreadRead($thread);                   // Participant

Show 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 crypto

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