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

Commentable Laravel Package

businesstilto/commentable

A Laravel package that makes any Eloquent model commentable through a simple trait. It provides polymorphic comment relationships, threaded replies, and ready-to-use migrations, so you can add comment functionality to existing applications without building the infrastructure from scratch.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install the package

    composer require businesstilto/commentable
    

    Check the package's composer.json for Laravel version constraints before upgrading — this is a small, young package (18 stars), so pin the version in composer.json rather than floating on dev-main.

  2. Publish and run the migration/config

    php artisan vendor:publish --provider="Businesstilto\Commentable\CommentableServiceProvider"
    php artisan migrate
    

    The published migration creates the polymorphic comments table (commentable_type, commentable_id, parent_id, and approval-related columns). Read the migration first — it is the most reliable source of truth for column names and nullability.

  3. Add the trait to a model

    use Businesstilto\Commentable\Traits\HasComments;
    
    class Post extends Model
    {
        use HasComments;
    }
    

    Start with one model (e.g. Post) and one use case: “authenticated users can leave a comment on a post.” Do not wire it into every model on day one.

  4. Where to look first

    • config/commentable.php (after publishing) — model swap, table name, approval defaults.
    • The HasComments trait — exact relationship and helper method names.
    • The package Comment model — fillable fields, scopes, and approval methods.
    • The migration — authoritative schema for parent_id, status/approved column, and indexes.
  5. First use case

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

    Then render $post->comments in a Blade view, wrapping output in authorization and approval checks. Confirm the default approval state before shipping — a comment may be created as pending and silently absent from the default relation.

Implementation Patterns

Basic polymorphic commenting

Use the trait's relation for creation and reading; avoid instantiating the Comment model directly so that polymorphic type handling and package defaults stay consistent.

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

$post->comments()->latest()->paginate(20);

For a dashboard “recent activity” feed, query the comment model directly and eager load the parent:

Comment::with('commentable')
    ->latest()
    ->limit(50)
    ->get();

Threaded replies

Threading is parent/child via parent_id. Keep the creation flow explicit rather than relying on a deeply nested form:

$parent = $post->comments()->findOrFail($request->integer('parent_id'));

$post->comment([
    'body' => $request->validated('body'),
    'user_id' => auth()->id(),
    'parent_id' => $parent->id,
]);

Practical patterns:

  • Limit depth in the UI, not just the database. A depth accessor or a recursive Blade partial with a max-level guard prevents runaway threads.
  • Load a whole thread once: fetch top-level comments with replies eager-loaded, then render recursively. Avoid lazy-loading replies per comment.
  • Validate parent belongs to the same commentable before saving. A parent_id from another post is a common bug and a potential data leak.

Approval workflow

Treat approval as a state machine on the comment model. The package exposes approval semantics, but you still own the policy layer:

// Public-facing read: approved only
$post->comments()->approved()->latest()->paginate(20);

// Moderation queue
Comment::pending()->with('commentable')->latest()->paginate(50);

// Approve
$comment->approve(); // verify exact method in the package model

Integration tips:

  • Add a CommentPolicy with view, create, update, delete, and an approve ability restricted to moderators.
  • Decide whether the author sees their own pending comment (“awaiting moderation” placeholder). This requires a scope that includes approved OR user_id = auth()->id().
  • Fire domain events after approval (e.g. CommentApproved) to trigger notifications. Keep this in your app, not in the package.

Query helpers and counts

Use scopes for filtering and aggregate counts for badges:

$post->comments()->approved()->count();
$post->comments()->where('user_id', $user->id)->exists();

For listing pages, avoid withCount('comments') unless the package relation supports constrained counts cleanly — if approval filtering is needed, use a constrained withCount(['comments' => fn ($q) => $q->approved()]) and verify the generated SQL.

Configurable models

If you need extra columns (e.g. edited_at, ip_address) or a custom moderation status:

  1. Publish the config.
  2. Create App\Models\Comment extends \Businesstilto\Commentable\Models\Comment.
  3. Point commentable.comment_model (exact key per config file) at your class.
  4. Add your columns in a separate migration — do not edit the published package migration after it has run in shared environments.

Do this before seeding production data. Swapping the model after comments exist is fine, but changing the table name is not.

UI integration

  • Render comment bodies through a sanitizer (e.g. HTML Purifier or e()), especially if you allow Markdown.
  • Use a recursive partial for threads and include a “Reply” form that posts parent_id.
  • Add a simple honeypot or rate limiter (RateLimiter::for('comments')) on the create route; the package does not handle spam.
  • Eager load user on comments to avoid N+1 when displaying author names.

Gotchas and Tips

  • Approval default is the biggest surprise. Verify whether a newly created comment is immediately visible or pending. Write a feature test asserting the public listing behavior before launch.
  • Polymorphic type drift. If you use Relation::morphMap(), register your commentable models before creating comments. Otherwise existing rows store FQCNs and break when you introduce a morph map later.
  • Model swapping and migrations. Changing the configured comment model does not change the table. If you rename the table via config, you must write your own migration and migrate existing rows.
  • Config caching. After changing config/commentable.php, run php artisan config:clear in local/CI. Stale cached config is a frequent cause of “my custom model isn't used” confusion.
  • N+1 in threaded views. The recursive partial is the usual culprit. Load top-level comments with replies eager-loaded, or accept a bounded number of queries and document it.
  • parent_id integrity. There is no guarantee the package enforces same-commentable parents. Validate in a FormRequest and consider a foreign key with nullOnDelete so deleting a parent does not orphan or cascade unexpectedly.
  • Deleting commentables. Decide between cascading deletes and soft deletes. Polymorphic relations do not get database-level cascades by default; add a deleting observer on commentable models to clean up comments, or rely on morphMany with cascade if the package supports it.
  • Authorization is yours. The package provides data access, not access control. Every create, approve, edit, and delete route needs a policy or gate check.
  • Sanitization is yours. Store raw input only if you sanitize on output; otherwise sanitize on input. Never render {!! $comment->body !!} without purification.
  • Testing strategy. Write feature tests for: create comment, pending visibility, approve action, reply nesting, unauthorized approve, and deletion of a parent comment. This is a small package; your tests are the safety net against breaking changes in minor releases.
  • Extension points to prefer. Custom Comment model, config-driven table/model names, model observers/events, and policies. Avoid overriding package internals or editing vendor files.
  • Maturity risk. With a low star count and recent dependabot-only releases, treat upgrades as potentially breaking even on patch versions. Pin exact versions in composer.json and review the changelog before composer update.
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.
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
noith/settings