focusreactive

payload plugin comments

by focusreactive
payload-plugins
Publishing workflow
v3
^3.0.0
MIT

Overview

Summary generated from the README

Adds a collaborative commenting system to the Payload v3 admin. It patches every collection and global to show comment badges on field labels, a per-document comments drawer and a global comments panel in the header. Comments are stored in an auto-generated collection, and mentioned users can be notified by email through Resend. Requires importing a stylesheet, regenerating the import map and a migration on SQL adapters.

Features

  • Field-level and document-level comments on collections and globals
  • @mention autocomplete with email notifications via Resend
  • Resolve and reopen comments with Open, Resolved and Mentioned me filters
  • Global comments panel in the admin header listing all comments
  • Optional multi-tenant scoping and locale-aware field comments
  • Cascade deletion of comments when the parent document is deleted

Use cases

  • Review content with editors by leaving comments on specific fields before publishing
  • Notify teammates by mentioning them in comments on a document
  • Track open and resolved feedback across multiple tenants or locales in one admin

Installation

npm i @focus-reactive/payload-plugin-comments

29

Stars

3

Forks

24

Open Issues

Today

Last Updated

0

Weekly Downloads

v1.11.3

npm Version

624.8 KB

Unpacked Size

7

Dependencies

Repository stars

Trends appear after 7 days of data. Daily snapshots have not started yet.

Shared by 11 plugins in focusreactive/payload-plugins.

npm weekly downloads

Trends appear after 7 days of data. Daily snapshots have not started yet.

Each point is npm's rolling 7-day download count.

Health Score

55/100Good

Composite score based on GitHub activity, stars, npm downloads, publish recency, dependency count, and package size.

Badge for your README

Payload Directory health badge
Markdown
[![Payload Directory](https://payloaddirectory.dev/badge/1177993877-payload-plugin-comments.svg)](https://payloaddirectory.dev/plugins/1177993877-payload-plugin-comments)
HTML
<a href="https://payloaddirectory.dev/plugins/1177993877-payload-plugin-comments"><img src="https://payloaddirectory.dev/badge/1177993877-payload-plugin-comments.svg" alt="Payload Directory"></a>
Updated Oct 20, 2026Created Mar 10, 2026Published Oct 12, 2026

README

@focus-reactive/payload-plugin-comments

npm

A collaborative commenting plugin for Payload CMS v3. Adds a full-featured comments system to the Payload admin panel — supporting document-level and field-level comments on both collections and globals, @mentions with email notifications, comment resolution, multi-tenancy, and locale-aware filtering.

Table of Contents


AI Integration Prompt

Copy and paste this prompt into your AI assistant (Cursor, Claude, etc.) to integrate the plugin into an existing Payload v3 project.

I want to add a collaborative commenting system to my Payload CMS v3 project using @focus-reactive/payload-plugin-comments.

## How it works

The plugin injects into every collection and global:
- A field-level comment badge on every field label — shows comment count, opens a popup to post
- A document-level comments drawer (sidebar) scoped to the current document
- A global comments panel (header button) listing all comments across every document and global

Comments are stored in an auto-generated `comments` collection (hidden from the sidebar). Each comment records:
- documentId / collectionSlug / globalSlug — what it belongs to
- fieldPath — dot-notation field path (null = document-level)
- locale — for field-level comments (null = shown in all locales)
- text — may contain @(userId) mention tokens
- author, mentions, isResolved, resolvedBy, resolvedAt
- tenant (optional, when multi-tenancy is enabled)

@mentions use autocomplete from the users collection. Mentioned users receive email notifications via Resend.
Comments are automatically deleted when their parent document is deleted.

## Installation

pnpm add @focus-reactive/payload-plugin-comments

## Step 1 — Register the plugin in payload.config.ts

import { buildConfig } from 'payload'
import { commentsPlugin } from '@focus-reactive/payload-plugin-comments'

export default buildConfig({
  plugins: [
    commentsPlugin({
      // Optional: specify which field to use as document title in the UI
      collections: [
        { slug: 'pages', titleField: 'title' },
        { slug: 'products', titleField: 'name' },
      ],
      // Optional: customize the display name field on users
      usernameFieldPath: 'name', // default
    }),
  ],
})

## Step 2 — Import the stylesheet

In your global CSS or admin layout:

@import "@focus-reactive/payload-plugin-comments/styles.css";

Or in a TypeScript/JS file:

import "@focus-reactive/payload-plugin-comments/styles.css";

## Step 3 — Regenerate the import map

Run this after adding the plugin so Payload registers the plugin's admin components:

npx payload generate:importmap

(Or the equivalent for your package manager: pnpm payload generate:importmap / bunx payload generate:importmap)
If you skip this step the comment badges, drawer, and header button will not appear in the admin UI.

## Step 4 — Create and run a migration (SQL adapters only)

The plugin adds a `comments` collection to your database. If you use PostgreSQL or SQLite, create and apply a migration:

npx payload migrate:create create_comments
npx payload migrate

Skip this step if you use the MongoDB adapter.

## Optional: Multi-tenancy

commentsPlugin({
  tenant: {
    enabled: true,
    collectionSlug: 'tenants',       // default
    documentTenantField: 'tenant',   // default
  },
})

## Optional: Collection overrides

commentsPlugin({
  overrides: {
    access: { /* custom access control */ },
    fields: (defaultFields) => [...defaultFields],
    hooks: {
      afterChange: [async ({ doc }) => { /* ... */ }],
    },
  },
})

## Environment variables (for email notifications)

RESEND_API_KEY=re_xxxxxxxxxxxxxxxx
[email protected]

## Important notes

- All collections and globals automatically get field-level comments — `collections` config only sets UI metadata (titleField).
- Comments on globals use globalSlug instead of collectionSlug/documentId.
- Field-level comments are locale-scoped; document-level comments show in all locales.
- The `comments` collection is hidden from the admin sidebar by default.
- Auto-cleanup: when a document is deleted, all its comments are deleted too.
- usernameFieldPath supports dot-notation (e.g. "profile.displayName").
- If RESEND_FROM_EMAIL is not set, mention emails are silently skipped.

UI Screenshots

Global Comments Panel

A header button in the Payload admin opens a drawer listing all comments across every document and collection.

Global comments panel

Document Comments Panel

When viewing a document, a side panel shows all comments scoped to that document with filter tabs (Open / Resolved / Mentioned me).

Document comments panel

Field Comment Popup

Clicking the comment badge on a field label opens a popup where you can write and post a new comment for that specific field.

Field comment popup

Field Label Button — Two States

The comment button embedded in the field label has two visual states: no comments (inactive, appears on hover) and one or more open comments (active, showing the count badge).

Inactive (no comments)Active (has comments)
Field label button — inactiveField label button — active

Features

  • Document-level comments — Leave comments on any document in any collection or global
  • Field-level comments — Comment directly on individual fields; the field label shows a badge with the comment count
  • Global support — Field-level and document-level comments work on Payload Globals, not just collections
  • @mention users — Mention other users in comments using @name autocomplete
  • Email notifications — Mentioned users receive email notifications via Resend
  • Resolve comments — Mark comments as resolved/unresolved; filter by open, resolved, or mentioned
  • Global comments panel — A header button opens a drawer showing all comments across all documents and globals
  • Optimistic UI — Comments appear instantly before the server confirms
  • Multi-tenancy — Scope comments to tenants via @payloadcms/plugin-multi-tenant
  • Locale-aware — Field-level comments are tied to a locale; document-level comments are shown in all locales
  • Auto-cleanup — Comments are automatically deleted when their parent document is deleted
  • i18n / Translations — All UI strings are translatable; ship your own locale overrides alongside the built-in English defaults

Prerequisites

  • Node.js 20 or higher
  • A working Payload CMS v3 project with Next.js
  • pnpm (recommended)
  • A Resend account (required only for mention email notifications)

Installation

pnpm add @focus-reactive/payload-plugin-comments
npm install @focus-reactive/payload-plugin-comments
# or
yarn add @focus-reactive/payload-plugin-comments

Peer dependencies: payload ^3.0.0, @payloadcms/ui ^3.0.0. next ^14 || ^15, react ^18 || ^19, react-dom, and @payloadcms/plugin-multi-tenant are optional.


Setup

1. Add the plugin to your Payload config

// payload.config.ts
import { buildConfig } from "payload";
import { commentsPlugin } from "@focus-reactive/payload-plugin-comments";

export default buildConfig({
  plugins: [
    commentsPlugin({
      // Optional: customize which field is used as the document title in the UI
      collections: [
        { slug: "pages", titleField: "title" },
        { slug: "products", titleField: "name" },
      ],
    }),
  ],
  // ... rest of your config
});

2. Import the styles

Add the plugin's stylesheet to your global CSS or admin layout:

/* global CSS */
@import "@focus-reactive/payload-plugin-comments/styles.css";

Or import it in a layout/page file:

import "@focus-reactive/payload-plugin-comments/styles.css";

3. Regenerate the import map

Payload needs its import map regenerated whenever you add or remove a plugin that registers admin components. Run:

npx payload generate:importmap

This command works regardless of your package manager (npm, pnpm, yarn, bun). Alternatively use the equivalent for your package manager:

pnpm payload generate:importmap
# yarn payload generate:importmap
# bunx payload generate:importmap

If you skip this step the plugin's admin components (comment badges, drawer, header button) will not appear in the Payload admin UI.

4. Create and run a migration

The plugin adds a comments collection to your database. If you use a SQL adapter (PostgreSQL, SQLite), create and apply a migration:

npx payload migrate:create create_comments
npx payload migrate

Skip this step if you use the MongoDB adapter — Payload creates collections automatically.


Configuration

The commentsPlugin factory accepts an optional CommentsPluginConfig object:

commentsPlugin(config?: CommentsPluginConfig)

CommentsPluginConfig

OptionTypeDefaultDescription
collectionsCollectionEntry[]—UI metadata for collections (title field). All collections get comments regardless.
enabledbooleantrueSet to false to disable the plugin entirely
tenantTenantPluginConfig—Multi-tenancy settings (see below)
overridesCollectionOverrides—Customize the generated comments collection
translationsTranslations—Override UI strings per locale (see below)
usernameFieldPathstring"name"Dot-notation path to the display name field on the users collection

CollectionEntry

Each entry in collections is an object that provides UI metadata for a specific collection:

interface CollectionEntry {
  slug: string;
  titleField?: string; // Field used as document title in the UI. Default: "id"
}

Note: You do not need to list every collection. All collections (and all globals) automatically support comments. The collections array only sets display metadata like the title field.

Examples:

commentsPlugin({
  collections: [
    { slug: "pages", titleField: "title" },
    { slug: "products", titleField: "name" },
  ],
});

usernameFieldPath

Specifies which field on the users collection is used as the display name in comment UI and @mention autocomplete. Supports dot-notation for nested fields.

commentsPlugin({
  usernameFieldPath: "name", // default
  // usernameFieldPath: "firstName",
  // usernameFieldPath: "profile.displayName",
});

TenantPluginConfig

Configure multi-tenancy when using @payloadcms/plugin-multi-tenant:

OptionTypeDefaultDescription
enabledbooleanfalseEnable tenant scoping
collectionSlugstring"tenants"Slug of the tenants collection
documentTenantFieldstring"tenant"Field on document collections that holds the tenant reference

Example:

commentsPlugin({
  tenant: {
    enabled: true,
    collectionSlug: "tenants",
    documentTenantField: "tenant",
  },
});

Collection Overrides

Use overrides to customize the generated comments collection — for example, to extend access control or add custom fields:

commentsPlugin({
  overrides: {
    access: {
      // Override the default "authenticated only" access
    },
    fields: (defaultFields) => [...defaultFields],
    hooks: {
      afterChange: [
        async ({ doc }) => {
          console.log("Comment changed:", doc.id);
        },
      ],
    },
  },
});

Translations

Use translations to override any UI string for one or more locales. Each key is a locale code; the value is a partial object of the CommentsTranslations shape — keys you omit fall back to the built-in English defaults.

commentsPlugin({
  translations: {
    fr: {
      label: "Commentaires",
      add: "Ajouter un commentaire",
      writeComment: "Écrire un commentaire",
      comment: "Commenter",
      cancel: "Annuler",
      resolve: "Résoudre",
      reopen: "Rouvrir",
      delete: "Supprimer",
      filterOpen: "Ouverts",
      filterResolved: "Résolus",
      filterMentioned: "Me mentionnent",
    },
  },
});

All translatable keys (with their English defaults):

KeyDefault (English)
label"Comments"
openComments_one"{{count}} open comment"
openComments_other"{{count}} open comments"
add"Add comment"
writeComment"Write a comment"
comment"Comment"
cancel"Cancel"
posting"Posting…"
resolve"Resolve"
reopen"Reopen"
delete"Delete"
general"General"
close"Close"
syncingComments"Syncing comments"
openCommentsAria"Open comments"
failedToPost"Failed to post comment"
failedToUpdate"Failed to update comment"
failedToDelete"Failed to delete comment"
failedToAdd"Failed to add comment"
unknownAuthor"Unknown"
deletedUser"Deleted user"
noOpenComments"No open comments"
noResolvedComments"No resolved comments"
noMentionedComments"No comments mentioning you"
filterOpen"Open"
filterResolved"Resolved"
filterMentioned"Mentioned me"
noMentionMatches"No matches"

The CommentsTranslations type is exported from the package so you can type your translation objects:

import type { CommentsTranslations } from "@focus-reactive/payload-plugin-comments";

const myTranslations: Record<string, Partial<CommentsTranslations>> = {
  fr: { label: "Commentaires" },
  de: { label: "Kommentare" },
};

commentsPlugin({ translations: myTranslations });

Environment Variables

Required for email notifications

VariableDescriptionExample
RESEND_API_KEYYour Resend API keyre_xxxxxxxxxxxxxxxx
RESEND_FROM_EMAILSender email address for mention notifications[email protected]

If RESEND_FROM_EMAIL is not set, mention email notifications are silently skipped and an error is logged to the console.

.env.local example:

RESEND_API_KEY=re_xxxxxxxxxxxxxxxx
[email protected]

Architecture Overview

How It Works

Plugin initialization (plugin.ts):

  1. The plugin receives a CommentsPluginConfig and returns a standard Payload Plugin function.
  2. It creates a comments collection (hidden from the admin sidebar by default).
  3. It patches every collection to inject FieldCommentLabel into each field's admin label and registers an afterDelete hook that cascade-deletes comments when a document is removed.
  4. It patches every global to inject FieldCommentLabel into each field's admin label.
  5. It registers an admin provider (CommentsProviderWrapper) and one admin action (CommentsHeaderButton).
  6. It registers REST endpoints under /api/comments-plugin/* (list, create, delete, resolve, mark-read, unread-mentions-count, mentionable-users, document-titles, field-labels). Each one requires an authenticated user and uses the Payload instance from the request, so it works on a freshly started serverless instance.

Data loading (src/api):

  • The admin UI fetches comments, document titles, mentionable users, field labels, and unread mention counts with TanStack Query from the plugin's endpoints.
  • Mutations (add, delete, resolve, mark as read) call the same endpoints and update the query cache optimistically.

State management (CommentsProvider):

  • Holds allComments in React state with optimistic updates via useOptimistic.
  • visibleComments is derived: filtered to the current document/collection/global/locale based on the Next.js pathname.
  • Exposes addComment, removeComment, resolveComment, and syncComments mutations.

Field-level comments (FieldCommentLabel):

  • The plugin overrides the Label component for every named field in every configured collection and global.
  • The label reads comments from context and filters by field path, showing a badge with the count.
  • Clicking the badge opens the comments drawer pre-scrolled to that field's comment group.

Comments collection schema:

FieldTypeDescription
documentIdnumberID of the document being commented on (null for globals)
collectionSlugtextSlug of the collection (null for global comments)
globalSlugtextSlug of the Payload global (null for collection document comments)
fieldPathtextDot-notation path of the field (null = document-level)
localetextLocale of the comment (null = shown in all locales)
texttextareaComment body (may contain @(userId) mention tokens)
mentionsarray → relationshipUsers mentioned in this comment
authorrelationship → usersComment author (set automatically)
isResolvedcheckboxWhether the comment is resolved
resolvedByrelationship → usersWho resolved it
resolvedAtdateWhen it was resolved
tenantrelationship (optional)Tenant scope (when multi-tenancy is enabled)

Exports Reference

Import pathExports
@focus-reactive/payload-plugin-commentscommentsPlugin, CommentsPluginConfig (type), CommentsTranslations (type)
@focus-reactive/payload-plugin-comments/styles.cssPlugin stylesheet (Tailwind-compiled CSS)

Available Scripts

Run these from the project root with pnpm:

CommandDescription
pnpm buildBuild the plugin to dist/ (tsup + Tailwind CSS minification)
pnpm devBuild in watch mode — rebuilds on file changes
pnpm lintLint + check formatting (Ultracite: oxlint + oxfmt)
pnpm lint:fixLint + fix, including formatting (Ultracite)
pnpm formatFormat files (Ultracite: oxfmt)
pnpm format:checkCheck formatting without writing

Contributing

  1. Fork the repository and create a feature branch.
  2. Install dependencies: pnpm install
  3. Start the build watcher: pnpm dev
  4. Make your changes in src/.
  5. Run pnpm lint and pnpm format:check before submitting.
  6. Open a pull request against main.

License

MIT © Focus Reactive

Similar plugins