Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Chatmessenger Laravel Package

syntaxlexx/chatmessenger

Built for Laravel, this chat and messaging package adds private conversations, real-time message delivery, and a ready-to-use messenger interface to your application. It includes models, migrations, events, and configurable options so you can get messaging running quickly with minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is a batteries-included chat backend: two models (Thread, Message), a participant pivot, a trait you attach to your User, and a small set of HTTP routes plus a Pusher broadcast event. You are expected to bring your own UI (Blade, Livewire, or Vue) and point it at the package's data layer.

1. Install and scaffold

composer require syntaxlexx/chatmessenger:^1.5.2
php artisan vendor:publish --provider="SyntaxLexx\ChatMessenger\ChatMessengerServiceProvider"
php artisan migrate

Version 1.5.2 is the first release with Laravel 12 support — if you are bootstrapping a new Laravel 12 app, pin ^1.5.2 explicitly rather than letting Composer resolve an older 1.x that predates the compatibility work. It is a patch release with no API changes, so everything below applies unchanged to earlier 1.x installs. Laravel 12 requires PHP 8.2+, so confirm your runtime matches the package's composer.json illuminate/* and php constraints before debugging anything else.

If the provider auto-discovery doesn't fire on your Laravel version, add SyntaxLexx\ChatMessenger\ChatMessengerServiceProvider to config/app.php (Laravel 11/12's slimmed bootstrap/providers.php still honours package discovery, so this is mostly a legacy-app concern). Publish assets separately (--tag=chatmessenger-assets) only if you intend to use the bundled views.

2. Give your user model chat abilities

use SyntaxLexx\ChatMessenger\Traits\ChatMessenger;

class User extends Authenticatable
{
    use ChatMessenger; // threads(), messages(), participant helpers
}

3. Configure before writing a line of UI

Open config/chatmessenger.php first — it is the single most important file to read. At minimum verify:

  • user_model — must be the fully-qualified class of the model that uses the trait (not a string alias, unless your morph map agrees).
  • Table names — change these now if you already have threads/messages tables in your schema.
  • Broadcasting / Pusher block — PUSHER_APP_KEY, PUSHER_APP_SECRET, PUSHER_APP_CLUSTER, and whether broadcasting is enabled.
  • Route registration flags / middleware — decide whether the package's own routes are exposed or whether you will wrap the models in your own controllers.

4. First use case: a direct message

$thread = ChatMessenger::createThread([$alice, $bob]);

$thread->sendMessage($alice, 'Hey Bob, are we still on for Friday?');

foreach ($thread->messages as $message) {
    echo $message->user->name . ': ' . $message->body;
}

That's the whole loop — create thread, send message, read messages. Everything else (group chat, unread badges, realtime) is a variation on those three calls.

Where to look next, in order: config/chatmessenger.php → database/migrations/* (to see the exact columns you can query) → src/Traits/ChatMessenger.php and src/Models/Thread.php (the real API surface, more authoritative than the README) → src/Events/* and the broadcast channel authorization → routes/.

Implementation Patterns

Two controller endpoints, not twelve. In practice you only need:

// ThreadController@index — list conversations for the sidebar
public function index(Request $request)
{
    $threads = $request->user()
        ->threads()
        ->with(['participants.user', 'latestMessage'])
        ->latest('updated_at')
        ->paginate(20);

    return view('chat.index', compact('threads'));
}

// MessageController@store — post a message to a thread
public function store(Request $request, Thread $thread)
{
    abort_unless($thread->hasParticipant($request->user()), 403);

    $message = $thread->sendMessage($request->user(), $request->validated('body'));

    return response()->json($message->load('user'));
}

The hasParticipant() / membership check is not optional — the package stores the relationship, but authorization on your routes is yours to enforce.

Direct vs. group is just participant count. createThread([$a, $b]) gives you a DM; createThread([$a, $b, $c]) gives you a group. Before creating a DM, look up an existing thread with exactly those two participants so you don't fork the conversation every time a user clicks "Message":

$thread = ChatMessenger::getThreads($user)
    ->first(fn ($t) => $t->participants->count() === 2
        && $t->participants->pluck('user_id')->sort()->values()->all() === collect([$a->id, $b->id])->sort()->values()->all());

$thread ??= ChatMessenger::createThread([$a, $b]);

Unread state is per-participant, not per-message. Mark read when the thread is opened, not when the message is fetched, otherwise your badge counts will never clear:

public function show(Thread $thread, Request $request)
{
    abort_unless($thread->hasParticipant($request->user()), 403);
    $thread->markAsRead($request->user()); // resets the participant's read cursor
    ...
}

Drive the sidebar badge from the same mechanism (unreadCount() on the thread / user) rather than a separate counter table you have to keep in sync.

Broadcasting is the reason people pick this package. Enable it in config, set BROADCAST_DRIVER=pusher, then authorize the private channel in routes/channels.php. The package emits an event on message creation with the thread ID; your frontend subscribes to that thread channel and appends. Three things are load-bearing here:

  • Laravel's channel callback must actually return true for participants, or Pusher auth will silently 403 in the browser console.
  • Your Event::dispatch path must be queued (ShouldBroadcast) and a worker must be running.
  • On Laravel 11/12 the channels.php route file must still be registered by BroadcastServiceProvider (or its withRouting(channels:) equivalent) — if realtime auth 404s on a fresh L12 app, this is why.

Sanitize at the boundary. Because body is stored raw and rendered by whatever Blade/Vue you write, either strip HTML on input (e.g. strip_tags or a markdown renderer with HTML disabled) or render with {{ }} only. Never {!! $message->body !!} on user input.

Extend via config, not by editing vendor code. The config lets you swap the Thread and Message model classes. If you need soft deletes, extra columns, or a broadcastAs() override, subclass the package model, point the config at yours, and add your columns in a separate migration.

Test without Pusher. Event::fake() around the send-message call lets you assert the broadcast was queued without a Pusher account:

Event::fake([MessageSent::class]);
$thread->sendMessage($user, 'hi');
Event::assertDispatched(MessageSent::class);

For feature tests, use BROADCAST_DRIVER=log and RefreshDatabase with the package's migrations included.

Gotchas and Tips

The user_model config mismatch is the #1 install failure. If the trait is on App\Models\User but the config still says App\User (the Laravel 5 default), thread creation throws a class-not-found or, worse, writes null participant rows. This is now more likely, not less: Laravel 12's skeleton moved everything under App\Models, and if you copied a config file from an older project (or upgraded in place), the stale default survives. Fix the config, then php artisan config:clear.

1.5.2 is a compatibility patch — treat it as a floor, not a feature. It adds Laravel 12 support and nothing else: no new methods, no schema changes, no deprecations. That means (a) upgrading is low-risk, and (b) the API surface documented above is still accurate, but also (c) nothing in the package's long-standing rough edges was fixed by it. Verify the method names in the source, not the changelog.

Migrations are published, not loaded from the package. If you upgrade the package, you will not get schema changes automatically — diff the package's database/migrations directory against your database/migrations on every composer update. This is a two-year-old schema with a recent release date, so schema churn is plausible; equally, don't assume a version bump shipped new columns, because 1.5.2 didn't.

Don't let composer update drift you backwards. If your composer.json says ^1.4 or similar, Composer will happily install 1.5.2 (fine), but on a Laravel 12 project a too-loose constraint combined with a --with-all-dependencies resolution can pull a version that predates L12 support. Pin ^1.5.2 on new installs and re-check after major framework upgrades.

Pusher credentials are read at runtime, not build time. Changing .env values requires php artisan config:clear (and a queue restart — php artisan queue:restart — plus php artisan config:cache if you cache in deploy). If realtime "works locally but not in production," check in this order: BROADCAST_DRIVER, config:cache staleness, queue worker alive, channel authorization in routes/channels.php, then the Pusher debug console (it shows rejected subscriptions explicitly, which the browser console usually doesn't).

N+1 queries will bite the thread list. Every thread renders its participants, its last message, and the last message's author. Always eager load (->with(['participants.user', 'latestMessage.user'])). On a 50-thread sidebar without eager loading you'll fire 150+ queries per page load.

Deleting a user leaves orphaned threads. There's no cascade magic beyond what the foreign keys define. Decide up front: soft-delete the user, or add an observer that removes the participant row and either deletes the thread (if now empty) or leaves a "deleted user" placeholder so the counterpart still sees history.

Check the route middleware before shipping. The package registers routes for convenience; if they're exposed without auth, any visitor can enumerate threads. Either disable package routes in config and build your own thin controllers (recommended — it also gives you clean authorization), or wrap the published route file in middleware(['web', 'auth']). Note the Laravel 11/12 middleware aliases (auth, web) are unchanged, but the default route-file registration path differs from L9 — if the package's routes vanish after upgrading the framework, that's the seam to inspect.

No pagination on messages by default. A long-running DM thread will happily return thousands of rows. Always slice: $thread->messages()->latest()->limit(50)->get()->reverse().

Debugging a "message sends but doesn't appear" report: check the database first (select * from messages order by id desc limit 5), then the event dispatch (Event::fake vs. real), then the queue table (jobs table growing = no worker). Nine times out of ten it's the worker.

Adoption is thin (0 dependents, ~100 stars) — read the source. Method names in the README occasionally lag the code (getThreads, createThread, sendMessage, markAsRead are the ones to verify against src/Traits/ChatMessenger.php in your installed version). Pin an exact version in composer.json rather than a bare ^, and budget an afternoon to read Thread.php and Message.php end to end — it's a small codebase and the read will save you more time than the docs will. The 1.5.2 changelog entry ("Laravel 12 Support") is a good sign of maintenance, but one line of release notes is not a substitute for reading the diff.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums