# laravel-comments: threaded comments for Eloquent models

> Beyond Code's laravel-comments package adds an approval-aware comment layer to any Eloquent model through a trait, with events and nested replies. It is a model layer rather than a UI: you get the data and the hooks, and you write the views and the routes yourself.

**beyondcode/laravel-comments** — Add comments to your Laravel application

- Repository: https://github.com/beyondcode/laravel-comments
- Website: https://beyondco.de
- Stars: 600 · Forks: 97
- Language: PHP
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/beyondcode-laravel-comments

## A trait turns any model into something commentable

laravel-comments exists to associate comments with your Laravel Eloquent models, with the two headline features being approval and nesting. The integration point is a trait, `BeyondCode\Comments\Traits\HasComments`, which you add to the models that should receive comments:

```php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use BeyondCode\Comments\Traits\HasComments;

class Post extends Model
{
    use HasComments;
    ...
}
```

Once a model has the trait, creating a comment is a method call on the model instance. The `comment` method takes the string you want to store and returns the newly created comment class:

```php
$post = Post::find(1);

$comment = $post->comment('This is a comment from a user.');
```

The second method, `commentAsUser`, takes a user model that should be associated with the comment, which is how you record a comment written on behalf of somebody else:

```php
$post = Post::find(1);

$comment = $post->commentAsUser($yourUser, 'This is a comment from someone else.');
```

Because the trait is per model, the same comment storage serves a blog post, a video and a product page without a separate package for each.

## Approval is a boolean you are responsible for

By default every comment starts unapproved. Under the hood that is a boolean column named `is_approved`, and the documentation is clear about what that means for your code: you are expected to use it in your views and controllers to filter out comments you do not want displayed yet.

Approving one comment is a method on the comment model:

```php
$post = Post::find(1);
$comment = $post->comments->first();

$comment->approve();
```

For automatic approval, the package offers a second route. Your User model implements the `BeyondCode\Comments\Contracts\Commentator` interface and defines `needsCommentApproval($model)`, which receives the model instance the comment is being added to and returns a boolean:

```php
namespace App\Models;

use BeyondCode\Comments\Contracts\Commentator;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable implements Commentator
{
    /**
     * Check if a comment for a specific model needs to be approved.
     * @param mixed $model
     * @return bool
     */
    public function needsCommentApproval($model): bool
    {
        return false;
    }

}
```

Returning true marks the comment as not approved, returning false marks it as approved. The method receives the model, so a rule such as approve only the author of the post can be expressed directly in the user class.

The honest limitation here is that approval is a single flag with no roles, no moderation queue and no audit trail. The package gives you the state, and the decision about who may flip it, when, and through which interface stays in your application.

## A comment is commentable, so replies need no extra code

Nesting is not a separate feature you configure. `BeyondCode\Comments\Comment` itself implements the `HasComments` trait, which means a comment is a commentable object and you can reply to it with exactly the same API:

```php
$comment = BeyondCode\Comments\Comment::first();
$comment->commentAsUser($user, "Hey there!");
```

Deletion has one switch. When you delete a comment, you may optionally want its nested replies deleted as well, and that behaviour is turned on by setting the `delete_replies_along_comments` property in the `config/comments.php` file to `true`. That file is published rather than shipped as a fixed set of defaults, so this is a config-level decision, not a per call argument.

Reading comments back uses the `comments` relation on any model carrying the trait, and an `approved` scope on the relation for the filtered version:

```php
$post = Post::find(1);

// Retrieve all comments
$comments = $post->comments;

// Retrieve only approved comments
$approved = $post->comments()->approved()->get();
```

The distinction between the two lines is worth pausing on. `$post->comments` loads the whole relation, while `$post->comments()->approved()->get()` issues a query, so the scope is also the form you need when a model has many comments and you do not want to load them all into memory.

## Installing means four artisan commands

The package is a normal Composer dependency and it registers itself:

```bash
composer require beyondcode/laravel-comments
```

The comments table lives behind a migration, so publish it before running migrations:

```bash
php artisan vendor:publish --provider="BeyondCode\Comments\CommentsServiceProvider" --tag="migrations"
```

```bash
php artisan migrate
```

The configuration file is published the same way:

```bash
php artisan vendor:publish --provider="BeyondCode\Comments\CommentsServiceProvider" --tag="config"
```

What you should end up with is a `comments` table created by the published migration, a `config/comments.php` file you own, and models that opt into commenting one trait at a time. The migration is published rather than auto-migrated on install, which is the behaviour you want in a package, since the table shape is part of the contract with your application and it is safer to review it before it runs.

There is no setup step for views, routes or controllers, because the package contains none. Everything visible to a user is yours to build on top of the model API.

## Two events are the hook for everything else

The package dispatches two events, and they are the intended integration point for the parts the package does not implement. `BeyondCode\Comments\Events\CommentAdded` is dispatched when a new comment is added, and `BeyondCode\Comments\Events\CommentDeleted` is dispatched when a comment is deleted.

With those two events you can add behaviour without touching the package: sending a notification to the post author, pushing a moderation task onto a queue, writing an audit entry, or reindexing a search index. Because the events cover creation and deletion but not approval, an approval audit trail has to be built on your own, for example by listening for your own code that calls `approve`.

That gap tells you something about the shape of this package. It is a small, focused data layer with a couple of extension points, not a commenting system with moderation workflows. Teams usually reach for it when they want comment storage without adopting a larger discussion package, and then build the moderation side themselves.

## MIT licence, a 1.x line and a quiet year

The package is MIT licensed, with the licence file at the root of the repository, and the credits go to Marcel Pociot and the contributors. Security issues are handled by emailing the maintainer directly rather than through the issue tracker.

On cadence, the release list shows 1.9.0 tagged on 2026-10-02, 1.8.0 on 2025-03-02 and 1.7.0 on 2024-12-09. The last push was on 2026-10-02, the same day as the 1.9.0 tag, and the repository is not archived. The default branch is `master`, and the changelog in the repository root is the place to read what changed between those versions.

Testing is part of the documented workflow, with a single command:

```bash
composer test
```

The repository carries a `phpunit.xml.dist` for the test configuration, a `tests` directory, a `database` directory that holds the migrations, a `config` directory, and a `composer.json`. There is also a `phpunit.xml.dist.bak` file sitting next to the live configuration, which is the kind of leftover that suggests the test setup was changed at some point and the old file kept. The package still sits on the 1.x line rather than 2.0, so reading the changelog before an upgrade is worthwhile.

## Conclusion

laravel-comments fits a Laravel team that wants comment storage, approval state and events on top of models it already has, and that is prepared to build its own routes, controllers and views. It is a poor fit if you expect a ready made comment section, because the package ships no rendering layer and no authorisation beyond a boolean flag. Before adopting it, add the `HasComments` trait and decide where `is_approved` filtering happens, since the flag is your responsibility to honour, and read CHANGELOG.md before upgrading because the package is still on the 1.x line.

## FAQ

### How do I add comments to a Laravel model?

Add the `HasComments` trait from `BeyondCode\Comments\Traits\HasComments` to the model class, then call `$post->comment('text')` or `$post->commentAsUser($user, 'text')` on an instance. The `comment` method returns the newly created comment class.

### How are comments approved in laravel-comments?

Every comment starts with the boolean flag `is_approved` set to false, and you filter on it in your views and controllers. A single comment is approved with the `approve` method on the comment model, and automatic approval is implemented by having the User model implement the `Commentator` interface and return false from `needsCommentApproval($model)`.

### Can I nest replies to comments?

Yes. `BeyondCode\Comments\Comment` itself implements the `HasComments` trait, so a comment can be commented on with the same methods. To delete replies along with their parent comment, set `delete_replies_along_comments` to `true` in `config/comments.php`.

### Does the package include views, controllers or routes?

No. The package provides the model layer, the approval flag, the relations and the events, and the rendering and request handling are left to your application. You publish the migrations and config with artisan vendor:publish and then build the interface yourself.

### Which events does laravel-comments dispatch?

Two. `BeyondCode\Comments\Events\CommentAdded` fires when a new comment is added, and `BeyondCode\Comments\Events\CommentDeleted` fires when a comment is deleted. There is no separate event documented for approval.

## Sources

- [beyondcode/laravel-comments on GitHub](https://github.com/beyondcode/laravel-comments)
- [License: MIT](https://github.com/beyondcode/laravel-comments/blob/master/LICENSE)
- [Project website](https://beyondco.de)
- [README](https://github.com/beyondcode/laravel-comments/blob/master/README.md)
- [Releases](https://github.com/beyondcode/laravel-comments/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/beyondcode-laravel-comments
