Adds inline comment threads to Payload's Lexical editor. Editors select a passage, comment in an admin sidebar, reply, resolve, reopen or delete threads. It plugs in through a Lexical feature plus a plugin that stores messages in a hidden collection. Requires Payload 3.90 or later and React 19, and works with SQLite, PostgreSQL and MongoDB.
Inline comment threads on Payload rich text (Lexical), in English and French.
Installation
npm i @nualt/payload-plugin-comments0
Stars
0
Forks
0
Open Issues
Today
Last Updated
0
Weekly Downloads
v0.1.0
npm Version
205.4 KB
Unpacked Size
0
Dependencies
Trends appear after 7 days of data. Daily snapshots have not started yet.
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
Composite score based on GitHub activity, stars, npm downloads, publish recency, dependency count, and package size.
Badge for your README
[](https://payloaddirectory.dev/plugins/1428525268-root)<a href="https://payloaddirectory.dev/plugins/1428525268-root"><img src="https://payloaddirectory.dev/badge/1428525268-root.svg" alt="Payload Directory"></a>README
Inline comment threads for Payload 3.90 and Lexical. Select a passage, comment in the admin sidebar, reply, resolve, reopen or delete a thread. Includes English and French; the panel follows the admin language. The plugin does not change your public frontend.
Tested with SQLite, PostgreSQL and MongoDB (replica set).

| Package / runtime | Supported versions |
|---|---|
| This plugin | 0.x targets Payload 3 |
| Payload, its Lexical editor, UI and translations | ^3.90.0 |
| React and React DOM | ^19 |
| Node.js | >=20.18.1 |
| pnpm (development and packaging) | ^9 || ^10 || ^11; CI uses pnpm 11 and Node 22 |
| Databases tested | SQLite, PostgreSQL 16, MongoDB 7 replica set |
Payload 4 is not supported yet. Releases follow Semantic Versioning; each plugin major version documents its Payload compatibility. See the CHANGELOG for release details.
pnpm add @nualt/payload-plugin-comments
The host must provide compatible versions of payload, @payloadcms/richtext-lexical, @payloadcms/ui (^3.90.0), react and react-dom (^19). Translation types also require @payloadcms/translations (^3.90.0).
import { commentsFeature, commentsPlugin } from '@nualt/payload-plugin-comments'
import { lexicalEditor, InlineToolbarFeature } from '@payloadcms/richtext-lexical'
import { en } from '@payloadcms/translations/languages/en'
import { fr } from '@payloadcms/translations/languages/fr'
import { buildConfig } from 'payload'
export default buildConfig({
// Keep your database, secret, auth collection and other existing configuration.
i18n: { supportedLanguages: { en, fr }, fallbackLanguage: 'en' },
collections: [{
slug: 'posts',
fields: [
{ name: 'title', type: 'text', required: true },
{
name: 'content',
type: 'richText',
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
InlineToolbarFeature(),
commentsFeature(),
],
}),
},
],
}],
plugins: [commentsPlugin({
collections: ['posts'],
usersCollection: 'users',
slug: 'comments',
})],
})
Regenerate the host import map and types after installing. commentsFeature() must be added to each rich text editor you want to annotate. The plugin adds only the sidebar UI field to the listed collections. Enable an inline or fixed Lexical toolbar so that the comment button is available.
Save a new document before commenting. Saving a comment stores its message separately; save the document to persist its text anchors. Draft messages are kept in browser session storage under payload-plugin-comments:drafts:.
| Option | Default | Purpose |
|---|---|---|
collections | Required | CollectionSlug[] receiving the sidebar. |
usersCollection | users | Auth collection referenced by author. |
slug | comments | Hidden collection storing messages. |
canModerate | Nobody | ({ req }) => boolean | Promise<boolean> grants updates/deletes on other authors' messages. |
disabled | false | Hides the sidebar and editor commenting UI; retains the comments collection, stored fields and MarkNode support. |
By default, any authenticated user can read comments. Only users authenticated through the configured usersCollection can create comments. Only the author can update/delete their messages, including resolving their own threads. The author is always forced to the authenticated user on create and cannot be changed on update. Deleting a root deletes all its replies only if the requesting user can delete each one; otherwise, the entire operation is refused before deleting any message. canModerate is evaluated on the server; the UI gets its result from the collection's /moderation endpoint. No role or user name field is required; display names fall back to email and then “User” / “Utilisateur”.
Permission refusals leave the entire thread intact on all three adapters. With the default transactions on PostgreSQL and MongoDB (replica set), integration tests also verify that an unexpected failure after two reply deletions rolls back the whole cascade, preserving all messages and parent links. MongoDB requires a replica set for these transactions. Without database transactions, an unexpected failure during a cascade (other than a permission refusal caught by the pre-check) can still leave a partial deletion. SQLite transactions remain disabled in the example and default test suite; enable them with sqliteAdapter({ client: { url: '...' }, transactionOptions: {} }) when rollback is required.
These permissions are independent of the host document's access rules: this plugin does not provide tenant or per-document isolation. If your application requires those, adapt collection access before exposing comments to its users. Local API calls follow Payload's usual overrideAccess behavior; use overrideAccess: false and user when enforcing user permissions. The cascade pre-check always enforces the requesting user's delete permissions on replies, including when the root is deleted with overrideAccess: true. Creation also requires a user from usersCollection for author attribution. Ownership checks compare both the auth collection and ID; an explicit canModerate callback may grant moderation to other authenticated identities.
documentCollection and documentId are indexed text fields. The parent relation points to the configured comments slug. Changing slugs after storing comments requires your own data migration. The plugin supports string and numeric document/user IDs. The admin uses serverURL and routes.api, including a customized API route.
All plugin UI strings use the plugin-comments namespace. Host overrides take precedence:
i18n: {
supportedLanguages: { en, fr },
translations: {
en: { 'plugin-comments': { comments: 'Discussion' } },
fr: { 'plugin-comments': { comments: 'Discussion' } },
},
}
CommentsTranslations and CommentsTranslationKeys are exported for typed custom components. Collection and field labels provide English/French objects. Relative dates use the current admin language.
Keep commentsFeature() registered even when commenting is disabled so stored mark nodes remain readable. Public rendering should unwrap each mark and render only its children. For Payload's React renderer:
import type { JSXConvertersFunction } from '@payloadcms/richtext-lexical/react'
import { RichText } from '@payloadcms/richtext-lexical/react'
const converters: JSXConvertersFunction = ({ defaultConverters }) => ({
...defaultConverters,
mark: ({ node, nodesToJSX }) => nodesToJSX({ nodes: node.children }),
})
// <RichText data={document.content} converters={converters} />
Do not fetch comment messages on the public site. Other renderers need the same transparent treatment of mark nodes. This plugin supplies no frontend component or migration from existing comment collections.
pnpm generate:importmap
pnpm generate:types
pnpm dev
pnpm build
pnpm lint
pnpm test:int
pnpm test:int:mongodb
pnpm test:e2e
The example at http://localhost:3000/admin defaults to SQLite (dev/dev.db) and seeds [email protected] / test. Use these credentials only for local development. Set DATABASE_ADAPTER=sqlite|postgres|mongodb to choose an adapter; PostgreSQL and MongoDB outside tests require DATABASE_URI. Both admin languages are enabled.
pnpm test:int always uses in-memory SQLite with no database daemon. pnpm test:int:mongodb starts and stops a single-member MongoMemoryReplSet, using an isolated database and a binary cache under node_modules/.cache/; the first run downloads MongoDB. PostgreSQL tests require a fresh disposable database. For example:
docker run -d --rm --name ppc-pg -e POSTGRES_PASSWORD=postgres -p 127.0.0.1:55432:5432 postgres:16-alpine
DATABASE_URI=postgres://postgres:[email protected]:55432/postgres pnpm test:int:postgres
docker stop ppc-pg
The suite exercises ownership, moderation, replies, cascading deletion, disabled configuration and Lexical mark serialization. The mid-cascade rollback test runs on PostgreSQL and MongoDB and is skipped on the default SQLite configuration, which has no transactions.
Source exports are used during development; publishing uses dist/. Build before packing or publishing. No publication is performed by these scripts.
The panel loads up to 200 messages per document. Reopening a resolved thread restores its highlight at the first matching text node, using the stored quote (up to 120 characters). Repeated passages or formatting split across nodes may need manual correction. These behaviors are retained from the original feature; pagination and more precise anchors are outside this port.
MIT.
payload lexical collaboration
Inline threaded comments on text selections in the Payload Lexical rich text editor
32119/wk14v3
payload plugin comments
Document-level and field-level comments with @mentions and resolution inside the Payload admin panel
7510.7k/wk29v3
plugin
On-page editing and draft preview links for Payload collections and globals on a Next.js frontend
75598/wk0v3 v4
pages
Adds a versioned pages collection with configurable page types, localized fields, drafts and autosave
593/wk0v4
payload better preview
Live preview with block hover highlighting and two-way scroll sync between the Payload admin and the preview
5733/wk16v3
changes button
Adds a Changes button that shows a field-by-field diff of unpublished changes against the published version
5615/wk181v3