filagora/filagora-messages
A Laravel and Filament package for adding private messaging to your application. It provides conversations, message threads, and a clean UI for sending and receiving messages between users, with straightforward installation and integration into existing Filament panels.
Install the package alongside your Laravel app and publish what it needs:
composer require filagora/filagora-messages
php artisan vendor:publish --tag=filagora-messages-config
php artisan vendor:publish --tag=filagora-messages-migrations
php artisan migrate
Look here first, in this order:
config/filagora-messages.php — conversation/message models, table names, and the guard/user resolver used for membership checks.PrivateMessage model and its relations — conversation(), sender(), and now attachments().filagora:* commands registered by the service provider, particularly filagora:adopt and filagora:messages:link-attachments.First real use case: send a message inside a private conversation, render it in a thread view, then add a file upload to the compose form. Once that works end-to-end, wire in the attachment authorization (MessageAttachmentAccess) before you expose any download route.
Attachments are an optional integration: they light up only when filagora/filagora-forums ^0.6 is installed. You can ship messaging first and add forums later — the link-attachments command exists precisely so you can retrofit the columns onto an already-running install.
Sending and replying. Message sends and replies both go through the same attachment-claiming step (ClaimMessageAttachments): the sender's own waiting files that the message body references are claimed for that message. In practice this means your flow is "upload file → keep it unclaimed → submit the message body referencing it → package claims it." Don't try to attach files after the message is created and expect them to appear; the claim happens at send time.
Render the thread with its attachments. Eager-load to avoid N+1 on long threads:
$conversation->messages()
->with(['sender', 'attachments'])
->latest()
->paginate(50);
Guard the relation in views when forums isn't installed — attachments() may resolve to an empty collection, but your view shouldn't assume the table exists if you run mixed environments (e.g. a subset of apps without forums).
Authorize downloads via MessageAttachmentAccess. Don't roll your own "is the current user in this conversation" check in the controller. The access class is what enforces "only people still in the conversation" — which matters when members are removed. Route the download through it:
abort_unless(MessageAttachmentAccess::allows($attachment, $user), 403);
Extending an existing forums install. Three migration paths, pick based on your situation:
database/migrations/forums into your app's migrations, ensuring its timestamp sorts after forums' own attachments table migration.php artisan filagora:messages:link-attachments to add private_message_id and private_conversation_id in place.AttachmentLinks run — it installs the columns safely from any partially-complete state.Schema-extension awareness. Once the columns exist, the forums snapshot must accept a table that has extra columns. The package declares them as a schema extension of the forums snapshot so filagora:adopt doesn't choke on the unknown columns. If you maintain your own adopt/snapshot tooling, mirror that declaration.
Integration tip. Keep the attachment-claim step inside the same transaction as message creation if you care about "message sent but file unclaimed" being impossible. The claim is the only thing tying a file to a message, so a partial failure leaves an orphaned waiting file rather than a broken message.
private_message_id / private_conversation_id on the attachment row rather than deleting the file. Your attachments() relation must therefore tolerate nulls, and you should decide explicitly what happens to the underlying file (storage cleanup job, retention policy). Don't assume deletion cascades.migrate. On SQLite this is especially easy to hit in tests.class_exists of the forums attachment model in a health check if this matters.filagora:adopt acceptance isn't automatic for other tables. Only the declared extension is tolerated. If you add your own columns to the same table, expect adopt to complain unless you extend the snapshot yourself.MessageAttachmentAccess encodes that; ad-hoc $conversation->hasMember($user) checks in a download controller will drift.^0.6 on forums and is suggested, not required. Pin it explicitly in your composer.json if you rely on attachments — a resolution that pulls an older forums version will leave the extension columns orphaned and the claim flow inert.private_message_id? (2) does the body actually reference the file? (3) was the file uploaded by the sender (only the sender's own waiting files get claimed)? (4) is the viewer still a conversation member per MessageAttachmentAccess?AttachmentLinks (idempotent schema reconciliation) and ClaimMessageAttachments (claim policy) are the two places to look if you need custom behavior — subclass or decorate these rather than editing published migrations or models.How can I help you explore Laravel packages today?