Plugins
Conversations

Conversations

Messages bound to any document, global or key of your own, with channels, threads, mentions and read state. Comments and reactions ship as extensions.

@10x-media/conversations adds conversations to Payload. A conversation belongs to a target: a document, a global, or a key of your own. Its messages live in channels, and each channel decides who reads and who writes. Threads are one level deep. Mentions go through a Lexical feature and end in a hook. Every user has their own read state, per channel and per thread.

The core has no opinion on what a conversation is for. comments() turns it into a Comments button on edit views, reactions() adds emoji reactions, and attachments() files on messages. A support ticket system or a chat room is another configuration of the same core, with a view you compose from the admin components or, on a website, from the headless hooks and the shadcn registry.

payload.config.ts
import { conversations, perTarget } from '@10x-media/conversations'
import { comments } from '@10x-media/conversations/comments'

const isStaff = ({ req }) => req.user?.collection === 'users'

export default buildConfig({
  plugins: [
    conversations({
      slug: 'comments',
      access: perTarget(({ req }) => Boolean(req.user)),
      channels: [
        { slug: 'internal', label: 'Internal', access: { read: isStaff, create: isStaff } },
      ],
      extensions: [comments({ collections: { posts: true } })],
    }),
  ],
})
The comments drawer on a person: channel tabs, the feed with reactions, a system note, and the composer

The model

  • Instance. One conversations() call, named by slug. It registers <slug>-messages and <slug>-reads and serves its endpoints under /api/conversations/<slug>. Call it again for another use case: comments, tickets, chat.
  • Key. A conversation is a string: collection:<slug>:<id>, global:<slug> or custom:<anything>. The target holds no reference to it, so no field is added to your collections.
  • Channel. An attribute of each message, with its own access. A target offers the channels you list for it.
  • Access. Conversation access answers for many targets in one batch, per target (every channel, some channels, or read only). Channel rules see only the user. Both collections are closed over REST; every read and write goes through the instance endpoints.
  • Thread. A reply points at its root. Roots count their replies atomically on Mongo and Postgres.
  • Read state. A cursor per user, conversation and channel, plus one per opened thread.
  • Transport. How browsers learn that something changed: polling by default, Server-Sent Events, or a Pusher-compatible service.
  • Extensions. Shape an instance, add per-message data, endpoints, stored fields, their part of a send, UI slots, composer addons and client data.

Entry points

ImportContents
@10x-media/conversationsconversations, perTarget, defineMessageType, defineExtension, postMessage, touchMessage, getInstance, projectUsers, key helpers, transports and buses
@10x-media/conversations/commentscomments()
@10x-media/conversations/reactionsreactions()
@10x-media/conversations/attachmentsattachments()
@10x-media/conversations/clientadmin components on @payloadcms/ui
@10x-media/conversations/reactheadless provider and hooks, no @payloadcms/ui
@10x-media/conversations/reactions/reactuseReactions
@10x-media/conversations/attachments/reactuseAttachmentsComposer, useAttachments
@10x-media/conversations/composerthe Lexical composer core, no @payloadcms/ui
@10x-media/conversations/rscserver components the plugin registers
@10x-media/conversations/i18ntranslation keys
@10x-media/conversations/typespublic types

Where to go next

On this page