NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Comments for Laravel

Reading & counting comments

Relations

$post->comments;          // every comment written on the post
$post->approvedComments;  // visible + approved top-level comments, ordered per config
$user->writtenComments;   // every comment authored by the user

$comment->actor;          // the model that wrote the comment (null for anonymous)
$comment->commentable;    // the model the comment was written on

The fluent query

Comments::query() returns a site-wide CommentQuery; Comments::for($subject) and Comments::byAuthor($author) return one already scoped — a read side that mirrors the write builder. Chain filters and ordering, then fetch:

use RoundlyConsulting\Comments\Facades\Comments;

Comments::for($post)->approved()->newest()->paginate(20);
Comments::for($post)->visible()->rootsOnly()->withReplies()->get(); // public replies only
Comments::for($post)->pending()->count();
Comments::byAuthor($user)->get();
Comments::query()->hidden()->newest()->get();   // site-wide, every subject

withReplies() follows the query’s visibility: on a visible() query (in either order) only visible + approved replies load, at every level; without visible() every reply loads, for a moderation view.

MethodPurpose
for($subject) / byAuthor($author)Scope the query — they also chain onto Comments::query().
approved() / pending() / hidden()Filter by moderation status.
visible()Visible flag on AND status approved.
rootsOnly()Top-level comments only.
withReplies()Eager-load nested replies up to max_depth — only visible + approved ones on a visible() query, every reply otherwise.
newest() / oldest()Order by created_at.
orderByLikesDesc() / orderByTrending()Rank by likes — see Likes & ranking.
mostReported() / reportedMoreThan() / withReportCounts()Moderation queues — see Reporting & auto-moderation.
get() / paginate($perPage = 15) / count()Fetch, paginate or count the matches.
approveAll() / hideAll() / deleteAll()Bulk moderation — see Moderation.
tap($callback) / query()Refine or take the underlying Eloquent builder.

Escape hatches

When the built-in filters aren’t enough, tap() hands you the underlying Eloquent builder and query() returns it:

Comments::for($post)
    ->visible()
    ->tap(fn ($query) => $query->where('created_at', '>=', now()->subWeek()))
    ->get();

$builder = Comments::for($post)->approved()->query(); // the underlying Builder<Comment>

Comment counts

Show “X comments” on list pages without counting per row. Both helpers set a comments_count attribute:

$post->loadCommentCount();        // sets $post->comments_count

Post::withCommentCounts()->get(); // adds comments_count without N+1

comments_count is what the public sees: every visible + approved comment on the subject, replies included — the same rows as Comment::visible(). Pending, hidden and visible(false) comments are never counted.

Comment attributes

$comment->comment;     // the body (stored in the `comment` column)
$comment->status;      // CommentStatus enum
$comment->visible;     // bool
$comment->parent_id;   // ?int — null for top-level comments
$comment->locked_at;   // ?Carbon — set when the thread under this comment is locked
$comment->mentions;    // CommentMention rows

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.