kirschbaum-development/commentions
Commentions is a Laravel package for adding comments and reactions to Eloquent models. Simple traits make models commentable and users commenters, with support for mentions, emoji reactions, and easy customization for displaying and managing comments in your app.
Install and publish in one pass.
composer require kirschbaum-development/commentions
php artisan vendor:publish --tag="commentions-migrations"
php artisan vendor:publish --tag="commentions-config"
php artisan migrate
Unlike many packages, the migrations are published, not auto-loaded. If comments silently don't work, this is almost always the cause.
Make a model commentable. Two steps, both required — the trait alone does nothing useful:
use Kirschbaum\Commentions\Contracts\Commentable;
use Kirschbaum\Commentions\Concerns\HasComments;
class Order extends Model implements Commentable
{
use HasComments;
}
First use case: an internal notes panel on a resource. Add the form component to a resource form (typically a dedicated "Comments" tab or section) or render the Livewire component directly on a custom page:
use Kirschbaum\Commentions\Filament\Forms\Components\Comments;
public static function form(Form $form): Form
{
return $form->schema([
// ...
Comments::make()->columnSpanFull(),
]);
}
For a standalone page or Blade view:
@livewire('commentions::comments', ['record' => $record])
Where to look first: the published config/commentions.php is the real map of the package — it tells you which model is the "commenter", how mentionables are resolved, which reactions are offered, and what the polling behavior is. Read it before reading the source. Then open Kirschbaum\Commentions\Comment to see the schema you'll be querying against.
The canonical first win is an Order or Ticket resource where support staff leave threaded notes with @-mentions of teammates. You get persistence, mention resolution, and emoji reactions without writing a single migration or Livewire component.
Treat it as an inbox, not a form field. The form component is convenient, but the higher-leverage pattern is a table action or a slide-over on the resource's view page, so commenters never leave the list view:
use Kirschbaum\Commentions\Filament\Actions\CommentsAction;
Tables\Actions\Action::make('comments')
->label(fn ($record) => $record->comments()->count() . ' comments')
->modalContent(fn ($record) => view('comments-modal', ['record' => $record]));
If a dedicated action class isn't available in your Filament version, wire the Livewire component into a modal body — same result, one line of Blade.
Mentions are a resolver problem, not a UI problem. The @ autocomplete can only offer what your config exposes. For anything beyond "all users in one tenant," point the mentionable resolver at a scoped query:
// config/commentions.php
'mentionables' => [
App\Models\User::class => [
'name' => 'name',
'avatar' => 'avatar_url',
],
],
Then scope it per-request with a service provider binding or an AppServiceProvider::boot() closure if you need tenant isolation. Do this before launch — retrofitting scoping after users have mentioned people across tenants is painful, because mention rows already exist.
Notifications belong in a listener, not in the component. The package records mentions; it doesn't decide who gets emailed. Hook the mention event (or poll comment->mentions) and dispatch your own notification:
Event::listen(CommentCreated::class, function ($event) {
$event->comment->mentions->each(
fn ($user) => $user->notify(new MentionedInComment($event->comment))
);
});
This keeps delivery, digesting, and quiet hours in your app where they belong.
Reactions as lightweight triage. With a constrained emoji set ('reactions' => ['👍', '👀', '✅'] in config), reactions become a poor-man's status field — "acknowledged" vs. "resolved" — without a state machine. Keep the set small and intentional; users will read meaning into whatever you offer.
Polling off, refresh on demand. Since 1.0.4 you can force the comments list to refresh instead of relying on the polling interval. The pattern that scales best: disable polling globally (or per component), then trigger a refresh at the moments that actually matter — after posting a comment, when a slide-over/modal opens, or on an explicit "new comments" affordance. This keeps idle index pages cheap while still feeling live inside the thread.
Testing. Comments are ordinary Eloquent models, so standard factories and assertDatabaseHas work. Test the authorization path explicitly: the package does not know your policies, so anyone who can reach the component can comment unless you gate it. Add the check yourself:
Comments::make()
->visible(fn ($record) => auth()->user()->can('comment', $record));
Or gate at the policy level with a comment ability on the commentable model.
Multi-model commentability. Because Commentable is a morph, one Comment table serves Order, Invoice, and Project at once. Build a single reusable CommentsPanel Blade component or Filament partial and reuse it across resources rather than copy-pasting schema definitions.
HasComments without implements Commentable (or vice versa) yields confusing errors — usually an empty comment list or a RelationNotFoundException on the morph.php artisan migrate runs after publishing. Commit the published migration files.Relation::enforceMorphMap(), the commentable_type values stored by Commentions must match your aliases. Mismatches surface as comments vanishing after a morph map is introduced — and old rows don't get rewritten.mews/purifier) at render time rather than trusting the editor.web and admin guards, or run queue jobs/impersonation, the "current commenter" can resolve to the wrong (or null) user. Check the config's commenter resolver and, if needed, bind it explicitly per guard.comments counts in the table query to avoid N+1 on the badge column:->withCount('comments')
Order will orphan its comments unless the morph is handled. Add a deleting hook on the commentable model, or a DB-level cleanup, or the table quietly grows forever.--tag="commentions-views", if available) rather than subclassing components. To change behavior, extend Kirschbaum\Commentions\Comment and rebind it in a service provider — verify the package resolves it from the container before relying on that.src/; when behavior surprises you, read the Livewire component first — that's where the query, mention parsing, and authorization assumptions actually live. The 1.0.4 refresh hook lives there too, so it's the first place to look if the list doesn't update when you expect it to.How can I help you explore Laravel packages today?