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

Laravel Messenger Laravel Package

genealabs/laravel-messenger

GeneaLabs Laravel Messenger adds database-driven threaded messaging to Laravel apps, with conversations between users or models, participants, polymorphic message types, and a simple API for sending, reading, and managing chat-style messages.

View on GitHub
Deep Wiki
Context7

Getting Started

First, resolve what the package actually is. The registry metadata calls genealabs/laravel-messenger a "flash notification module," but the package's own description describes persisted, thread-based private messaging — conversations, messages, participants, unread tracking, soft deletes, migrations, Eloquent traits. Those are different products with different blast radius. On a dormant package, the source is the only trustworthy spec:

git clone https://github.com/mikebronner/laravel-messenger
cd laravel-messenger && git checkout 7.0.2
cat composer.json                 # illuminate/* and php constraints decide installability
ls src/ src/Models 2>/dev/null
ls database/migrations resources/views 2>/dev/null

Write the answer down next to the dependency in your notes. You will need it in six months when something breaks and nobody remembers why the package is in the lockfile.

Check installability before you invest an afternoon. The last release was 2020-03-06, so constraints target the Laravel 5.5–7 era. On Laravel 8+ expect resolution to fail:

composer require genealabs/laravel-messenger:^7.0 --dry-run
composer why-not genealabs/laravel-messenger:^7.0   # names the exact blocker

Do not reach for --ignore-platform-reqs or hand-edited constraints to force it in. That debt surfaces during a framework upgrade, on a deadline, in an unrelated branch — and now you are the maintainer of a fork.

Publish, then read what landed, before you keep it:

php artisan vendor:publish --provider="GeneaLabs\LaravelMessenger\Providers\ServiceProvider"

Read the config (what is swappable), the migrations (column and index shape), the views (markup and escaping assumptions), and the service provider (container bindings). Then decide what to keep.

First use case — flash reading. One call in the controller, one include in the base layout only:

// app/Http/Controllers/PostController.php
public function store(StorePostRequest $request)
{
    $post = Post::create($request->validated());
    Messenger::success('Post saved.');   // confirm the real facade name in src/
    return redirect()->route('posts.index');
}
{{-- resources/views/layouts/app.blade.php --}}
@include('vendor.laravel-messenger.alerts')   {{-- confirm published path --}}

Submit a form, load the redirect target, verify the toast renders exactly once. Zero or twice are the two classic failure modes and you want to find them on day one, not in QA.

First use case — thread reading. Put the trait on User, create a conversation with participants, post a message, query unread:

$conversation = Conversation::create(['subject' => 'Kickoff'])
    ->addParticipant($alice)
    ->addParticipant($bob);

$conversation->messages()->create([
    'user_id' => auth()->id(),
    'body'    => 'Here is the plan.',
]);

$conversation->markReadBy(auth()->user());
auth()->user()->unreadConversations()->count();  // verify scope names in src/

Where to look first, in order: src/ for the actual public API (facade methods, model methods, scopes), database/migrations for the schema you are inheriting, the published views for markup assumptions, and the service provider for bindings. The README is a hint, not a spec.

Implementation Patterns

Wrap the API on day one, whichever reading holds. The highest-value habit on a dormant dependency is a thin app-level boundary so removal is a use-swap rather than a refactor:

// app/Support/Messenger.php
namespace App\Support;

class Messenger
{
    public function success(string $m): self { return $this->flash('success', $m); }
    public function error(string $m): self   { return $this->flash('error', $m); }
    public function info(string $m): self    { return $this->flash('info', $m); }
    public function warning(string $m): self { return $this->flash('warning', $m); }

    protected function flash(string $level, string $message): self
    {
        session()->flash("messenger.{$level}", $message);
        return $this;
    }
}

Bind it over the original so no call site references the package namespace:

// app/Providers/AppServiceProvider.php
public function register(): void
{
    $this->app->bind(\GeneaLabs\LaravelMessenger\Messenger::class, \App\Support\Messenger::class);
}

Render flash from exactly one place. Include the partial only in the base layout. Including it in individual pages as well means double-render on redirect chains, or a silently consumed session key.

Route validation failures through the same channel so users see one consistent surface instead of a toast plus a stray @error block:

if ($validator->fails()) {
    Messenger::error($validator->errors()->first());
}

Authorize at the conversation level, not the route. Dormant packages did not solve tenancy or per-participant access control for you. Add a policy that checks the participants pivot:

$this->authorize('view', $conversation);   // ConversationPolicy

Compute unread in one query. The naive per-conversation query is an N+1 that bites as soon as anyone has a real inbox:

$unread = $user->conversations()
    ->withCount(['messages as unread_count' => fn ($q) => $q
        ->whereNull('read_at')
        ->where('user_id', '!=', $user->id)])
    ->get();

Publish and own the views. Copy the partials into your own Blade components so styling and accessibility stop being a package concern:

{{-- resources/views/components/flash.blade.php --}}
@foreach (['success', 'error', 'info', 'warning'] as $level)
    @if (session()->has("messenger.{$level}"))
        <div role="alert" aria-live="polite" class="flash flash--{{ $level }}">
            {{ session("messenger.{$level}") }}
        </div>
    @endif
@endforeach

Test the contract, not the package. Two or three feature tests are your entire safety net:

public function test_flash_survives_redirect_and_renders_once()
{
    $this->post('/posts', ['title' => 'x'])->assertRedirect('/posts');

    $this->get('/posts')
        ->assertSee('Post saved.')
        ->assertDontSee('Post saved.', false);   // guard against duplicate render
}

Plan for real-time separately. Neither reading provides delivery, presence, or typing indicators. If "messages appear instantly" is on the requirement list, that is Broadcasting plus Reverb/Soketi/Pusher layered on top — scope and budget it independently.

Model swapping via config is your cleanest extension point: point the config at subclasses of the package's User/Conversation/Message models and keep your domain logic out of vendor/.

Gotchas and Tips

The metadata mismatch is a real risk, not a typo. A package that cannot describe itself consistently is a package whose docs you should not trust mid-incident. Clone, read src/, and record the verdict alongside the dependency.

Composer will be your first wall. March 2020 illuminate/PHP constraints mean Laravel 9–12 will very likely refuse to resolve. Confirm with composer why-not rather than guessing — and treat "we'll fork the constraints" as a decision that makes you the maintainer of a fork.

Do not expect upstream. No releases in five years, no security policy, no PHP 8.2+ guarantee. Every framework major becomes a manual patch-or-replace. Your dependency scanner will flag it forever with no remediation path, and re-dispositioning that finding each audit cycle is an ongoing cost.

Bootstrap markup assumptions. Published views typically target Bootstrap 3/4 alert classes. On Bootstrap 5 or Tailwind every class is wrong, and you are maintaining a fork of a wrapper — at which point the wrapper's value is zero. Override deliberately, or write the fifteen lines yourself.

XSS surface. Check whether the partial uses {!! !!} or supports raw output. If either is true, never let user-derived content reach that path. Escape by default; if you need HTML, construct it server-side from a whitelist, never from request input.

Flash lifecycle traps:

  1. A redirect chain where the intermediate response is never rendered drops the flash silently.
  2. Including the partial in both layout and page double-renders.
  3. A route outside the web middleware group has no session and therefore no flash.
  4. Multiple app servers require a shared session store (Redis/DB) or sticky sessions — a session config issue, not a package issue, and identical for any replacement.

Thread semantics are product decisions the schema forces. Soft deletes on both threads and messages define what "Delete conversation" promises, what survives account deletion, and what your retention statement can claim. Decide it explicitly and write it down; this is the genuinely useful thing the package hands you, but only if you actually make the call.

Pivot constraints. Add a unique index on (conversation_id, user_id) yourself if the migration lacks one — duplicate participants produce duplicate unread counts and confused UIs. Index the read-state column too; unread badge queries scan it on every page load.

Debugging a vanished toast. Dump the whole session rather than guessing:

dd(session()->all());

Then php artisan view:clear after publishing, and confirm which published path your @include actually resolves to.

Pin the exact tag and vendor if you must keep it. Use ^7.0.2, or a path/VCS repository pointing at a fork so you have a place to land PHP 8 fixes. Never float.

Know your exit cost before you start. Count call sites that touch the facade/helper versus those reading the session key directly. Twenty call sites is a day of wrapping and a trivial removal. Two hundred — or any persisted conversation data — is a data-migration workstream: dual-write, backfill, cut over, decommission. That is not a composer remove.

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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource