core-comments
Polymorphic threaded comment system that attaches to any content type, with moderation.
core-comments
Primitive Free
core-comments is a polymorphic threaded comment system. It attaches to any content type via a targetType / targetId pair, supports nested replies, and ships a moderation queue plus ready-to-use public React components.
What it does
- Polymorphic — attach comments to any content type via a
targetTypestring. - Threaded — nested replies via a self-referencing
parentId(clients build the tree up to depth 3). - Moderation — pending/approved/rejected/spam statuses with an admin queue.
- Auto-approve — comments from logged-in users are approved immediately.
- Cursor pagination and batch counts (
countMany) to avoid N+1 on list pages. - Ships public components:
CommentSection,CommentForm,CommentItem,CommentCount.
Installation
bun run indigo add core-comments
After installing, generate and apply the schema:
bun run db:generate
bun run db:migrate
Configuration
Dependencies are wired through setCommentsDeps() in config/deps/comments-deps.ts:
sendNotification— notifies the parent comment's author on a reply (self-replies are skipped).onCommentCreated?— optional lifecycle callback (e.g. record an activity event).onCommentDeleted?— optional lifecycle callback.
import { setCommentsDeps } from '@/core-comments/deps';
import { sendNotification } from '@/server/lib/notifications';
import { recordActivity } from '@/core-activity/lib/activity-service';
setCommentsDeps({
async sendNotification({ userId, title, body, url }) { /* ... */ },
onCommentCreated(event) { recordActivity({ /* ... */ }); },
onCommentDeleted(event) { recordActivity({ /* ... */ }); },
});
Schema
| Table | Notable columns |
|---|---|
cms_comments |
targetType, targetId (polymorphic target), parentId (threading, nullable self-reference), userId, authorName (display override), content, status (0 pending / 1 approved / 2 rejected / 3 spam), deletedAt (soft-delete) |
API
commentsRouter (mounted as comments):
| Endpoint | Access | Purpose |
|---|---|---|
comments.list |
public | Approved comments for a target (cursor-paginated) |
comments.count |
public | Approved comment count for a target |
comments.countMany |
public | Batch counts for grids (max 100 IDs) |
comments.create |
protected | Post a comment (auto-approved) |
comments.update |
protected | Edit your own comment |
comments.delete |
protected | Soft-delete your own comment |
comments.adminList |
admin (content) |
Paginated list with status/type/search filters |
comments.updateStatus |
admin (content) |
Moderate a comment (approve/reject/spam) |
comments.adminDelete |
admin (content) |
Permanently delete a comment |
comments.statusCounts |
admin (content) |
Counts per status for filter tabs |
Components
| Component | Purpose |
|---|---|
CommentSection |
Main public component — comment form, threaded list, count header. Props: targetType, targetId |
CommentForm |
Textarea + submit. Props: targetType, targetId, optional parentId, onSubmitted, onCancel, autoFocus |
CommentItem |
A single comment with avatar, name, time, content, and reply/edit/delete actions |
CommentCount |
Inline count badge. Props: targetType, targetId, optional className |
Integration
Drop CommentSection into any page template:
import { CommentSection } from '@/core-comments/components/CommentSection';
<CommentSection targetType="post" targetId={post.id} />
For inline count badges on list pages:
import { CommentCount } from '@/core-comments/components/CommentCount';
<CommentCount targetType="post" targetId={post.id} />
The moderation queue is available at /dashboard/comments (nav item under the content group).