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 Comment Laravel Package

mll-lab/laravel-comment

Add comments to any Laravel Eloquent model with a reusable commentable trait, migrations, and a ready-made Comment model. Supports polymorphic associations and straightforward integration into existing apps.

View on GitHub
Deep Wiki
Context7

Getting Started

Confirm the floor before anything else. 2.0.0 requires PHP 8.2+ and Laravel 10–13. Run composer why-not mll-lab/laravel-comment against your real lockfile rather than trusting the constraint line — a major bump can tighten transitive pins internally.

Install in three commands:

composer require mll-lab/laravel-comment
php artisan vendor:publish   # pick the package's migration + config tags
php artisan migrate

If the provider registers publish tags, be explicit: --tag=comment-migrations and --tag=comment-config are the conventional names — check the service provider rather than guessing.

Read the source before the README. The package is small (trait + model + provider + migration), and the documentation is thinner than the code. Spend ten minutes in vendor/mll-lab/laravel-comment/src:

  • The trait tells you the relationship name and whether it exposes any helper methods.
  • The Comment model tells you the fillable columns (commentable_id, commentable_type, user_id, parent_id, and the body column), which is the real API contract.
  • The published migration tells you the column types, nullability, and — critically — which indexes already exist. You will be adding your own.

First use case: comments on a Post.

use MllLab\LaravelComment\...\Commentable;   // confirm namespace from the trait file

class Post extends Model
{
    use Commentable;
}

Then:

$post->comments()->create([
    'user_id' => auth()->id(),
    'body'    => $request->validated('body'),
]);

Render with $post->comments()->whereNull('parent_id')->with('author')->latest()->paginate(20)->withQueryString(). That is the whole happy path — everything past this point is work the package deliberately does not do for you.

Implementation Patterns

The package is a persistence layer, not a comment system. It gives you a polymorphic relation and a threaded schema. There is no UI, no moderation, no spam protection, no notification, no authorization, and no depth limit. Budget for building each of those; treat the package as saving you the migration and the relationship, nothing more.

Create a CommentPolicy on day one. The package ties comments to an author but almost certainly does not gate edit/delete. Without a policy, any authenticated user can PATCH any comment the moment you expose an endpoint:

public function update(User $user, Comment $comment): bool
{
    return $user->id === $comment->user_id;
}

Call $this->authorize('update', $comment) in the controller, not in the view.

Guard threading at write time. When accepting a parent_id, verify the parent belongs to the same commentable and cap the depth:

$parent = Comment::findOrFail($request->parent_id);
abort_unless(
    $parent->commentable_type === $post->getMorphClass()
    && $parent->commentable_id === $post->getKey(),
    422
);
abort_if($parent->depth() >= 3, 422);   // your own helper

Otherwise a crafted request re-parents a comment onto an unrelated model, and the tree becomes unwalkable.

Never eager-load the whole tree. with('children.children.children') is fine for a demo and fatal in production. Load one level per request — top-level comments paginated, then fetch children for the visible IDs — or use a recursive CTE if you need the full thread in one query. Always eager-load author; the N+1 on a 20-comment page is 21 queries.

Return a resource, not a model. If the front end is Livewire, Inertia, or Vue, shape the created comment into a CommentResource so the client can append it without a refetch:

return new CommentResource($comment->load('author'));

Use withCount for badges. Post::withCount('comments') is the cheap path for "12 comments" in an index. If you only want top-level counts, define a second relation on your model scoped to whereNull('parent_id') and count that instead — the two numbers differ and users notice.

Hook notifications through a model observer, not the controller, so comments created from seeders, imports, or an admin panel behave identically. Comment::observe(CommentObserver::class) and notify the commentable's owner on created, skipping self-replies.

For moderation, extend rather than fork. Add a status column in your own migration and a visible() scope or a visibleComments() relation on the trait-using model. Keep the package's migration untouched so vendor:publish upgrades stay clean.

Decide your morph strategy before the first row is written. If you plan to call Relation::enforceMorphMap(), register the aliases now. Adding it after comments exist means rewriting every commentable_type string in the table.

Gotchas and Tips

The 2.0.0 changelog does not list breaking changes. It documents only raised floors and Laravel 13 support. A major version with no upgrade guide means the blast radius is unknown — diff the source against your 1.x install, focusing on config keys, published migration columns, and any method signatures on the trait or Comment model. Do this in a branch with tests, not in production.

Deleting a parent probably orphans its children. There is no cascade in the schema unless the migration declares one. Pick a strategy and implement it explicitly: a ON DELETE CASCADE foreign key on parent_id, a soft delete plus a "deleted" tombstone render so the thread stays readable, or reparenting children to the grandparent. Silent orphans — children that exist but are unreachable because their parent is gone — are the classic failure mode here.

Comment::create() bypasses the morph auto-fill. $post->comments()->create([...]) sets commentable_id and commentable_type for you. Creating on the model directly requires both keys, and a mismatched pair produces a comment that exists but never appears in any list.

Check the author column against your auth model. If the package's config or migration assumes App\Models\User and you use a different model or a UUID key, the FK will fail on migrate or on insert. Verify the config key and the migration's foreignId type before running php artisan migrate on anything shared.

Add your own indexes. The default (commentable_type, commentable_id) index is necessary but not sufficient. Add (commentable_type, commentable_id, parent_id, created_at) for thread queries and a plain parent_id index for child fetches once the table passes a few hundred thousand rows.

Config and migration cache. After publishing or editing config, run php artisan config:clear. On deploy, the migration is only published once — new package versions that alter the schema will not re-publish over your existing file, so read the changelog for migration changes rather than assuming vendor:publish --force is safe.

Write your own factory. Packages this size rarely ship one. A CommentFactory with a reply() state (parent_id set to an existing comment on the same commentable) pays for itself the first time you test depth limits.

Pagination interacts badly with trees. Paginating top-level comments is straightforward; paginating replies per-parent is not. Either load all children for the current page's parents, or add a "load more replies" endpoint scoped to a single parent ID.

Version treadmill. 2.0.0 moves the floor to PHP 8.2 / Laravel 10 and tracks Laravel 13. Assign someone to check the package's support window quarterly — if it ships majors quickly and drops prior lines, missing one release compounds into a mandatory upgrade on someone else's schedule rather than yours.

Six stars is a real signal. Read the source before you depend on it, pin the version in composer.json, and keep your rendering layer decoupled enough that swapping the persistence out is a weekend, not a rewrite.

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.
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
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect