spatie/laravel-comments
Laravel Comments lets you associate comments with any Eloquent model, so you can add threaded discussion to posts, products, or other records with minimal setup. Configurable, tested, and maintained by Spatie.
Start with the hosted docs, not the README — the README is a stub pointing at https://spatie.be/docs/laravel-comments, and the useful pages are Installation, Adding comments to a model, Reactions, Notifications and Mentions.
composer require spatie/laravel-comments
php artisan vendor:publish --tag="comments-config"
php artisan vendor:publish --tag="comments-migrations" # inspect the stub before migrating
php artisan migrate
Then three wiring steps, in this order:
config/comments.php is the real API surface. Note the model mappings (comment model, commentator model, reaction model, notification classes) and the behaviour switches (auto-approval, guest comments, validation rules, gravatar/FontAwesome, sanitizer). Change class mappings before you migrate in anger; changing the comment model later means a data migration.Post), and the commentator trait on User (or whatever you point the config at). One trait gives you the comments morph-many relation; the other lets a user author/receive comments.Post page. Either drop in the shipped Blade component (<x-comments::comments :model="$post" />-style, per the docs page for your version) to get form + list + reactions for free, or ignore the views and call the model API directly from your own controller when you need custom auth, throttling, or an Inertia/JSON response.Before writing a line of app code, open vendor/spatie/laravel-comments/src for ten minutes. The public docs are good but thin on edge cases, and this is a small-adoption package (32 stars, 0 dependents in this snapshot) — the source is the secondary documentation.
One morph relation, many commentable types. This is a polymorphic package by design: enable comments on Post, Page, Ticket and query per-type. Register an explicit morph map in a service provider early; the package has historically needed it for helper methods such as resolving comment participants (see the 1.7.1 fix), and it keeps your commentable_type column sane.
Threaded discussions. Comments carry a parent reference, so replies are first-class: render top-level comments with children eager-loaded, and let the package's reply notifications handle the "someone answered you" mail. Don't build your own reply table on top.
Reactions as a separate axis. Reactions live alongside comments, not inside them: $comment->react(...), a hasReacted-style check, and aggregated counts. Typical pattern is a toggle endpoint (react/unreact) that returns the new count, with the UI optimistic and the server authoritative. If you only need likes, skip reactions entirely — it's optional surface area.
Authorize at the edges, not in the package. The shipped controllers do the obvious thing, but real apps need policies: who may edit/delete a comment, who may comment on a locked thread, who may approve. The cleanest pattern is a thin app-level controller that checks a Gate/policy and then delegates to the package's models, or publishes the views and wraps the form in @can.
Moderation as a state machine. Auto-approve is a config flag; for anything public-facing flip it off and gate visibility on an approved scope/attribute, with an admin action to approve/disapprove. Combined with mentions and notifications, this is the difference between a demo and a shippable comment box.
Notifications. Point the config's notification classes at your own subclasses (extends the package notifications), implement ShouldQueue, and route per-channel. The package respects a "disable notifications" preference on the user — honour it in anything you add.
Custom models. Extending the base Comment and remapping the config is the supported extension point: add scopes, casts, LogsActivity, or a rendered_html accessor there rather than in a wrapper. Same for the reaction model. Watch the custom-model-key support if you use UUID/ULID keys.
Frontend reality check. The shipped UI is server-rendered Blade with a JS sprinkle and (by default) pulls assets from a CDN. If you're on Livewire, Inertia, or a design system, budget for publishing the views or building your own UI on the model API — that's usually less work than fighting Tailwind classes you don't own.
--tag="comments-views") or the first CSP audit will be unpleasant.<img onerror>.commentable_type is a raw FQCN. Register the morph map before you seed data.^2.3 constraint is reasonable today; blindly floating across majors is not.with('commentator') and withCount('reactions') (or eager-load reactions) before rendering a thread; a 50-comment page with lazy gravatars and per-row reaction queries will crawl.null. Set an explicit gravatar fallback rather than shipping mystery-person icons.throttle middleware on your comment endpoint and consider honeypot/akismet-style checks before the model is touched.composer test), MIT licensed, releases through 2026 — but 0 dependents means few Stack Overflow answers. Budget time to read source and write feature tests around approval, edits, deletion, and notifications before you ship publicly.How can I help you explore Laravel packages today?