Enable mentions
Turn on the mention picker by giving the comments provider a list of users, and query for the mentions that result.
The mention picker is off until you give the provider a list of users who can be mentioned. This guide turns it on, and covers how to query for the mentions that result.
Prerequisites:
- A working comments panel. See Get started.
- Read access to the project's users.
useUsers()reads them by default. For the organization's users instead, calluseUsers({resourceType: 'organization', organizationId}).
Build the mention options
Each option is a user, plus one flag:
interface MentionOption {
id: string
displayName?: string
imageUrl?: string
/** `false` renders the option as unavailable. */
granted?: boolean
}There is no mention-fetching hook in @sanity/collaboration: which users may be mentioned on a document is a permissions question, and it belongs in the SDK. Until a permission-aware read exists, map useUsers. Note the shape change, since sanityUserId becomes id and the profile is flattened:
import type {MentionOption} from '@sanity/collaboration'
import {useUsers} from '@sanity/sdk-react'
import {useMemo} from 'react'
export function useMentionOptions(): MentionOption[] {
const {data} = useUsers()
return useMemo(
() =>
data.map((user) => ({
id: user.sanityUserId,
displayName: user.profile.displayName,
imageUrl: user.profile.imageUrl,
})),
[data],
)
}Pass the mention options to the provider
SDKCommentsProvider and CommentsProvider both take mentionOptions, and every CommentInput below it gets the picker, including the reply composer inside a thread card and the inline editor a comment opens for editing.
import {SDKCommentsProvider} from '@sanity/collaboration/sdk'
import {useMentionOptions} from './useMentionOptions'
// Inside a component:
const mentionOptions = useMentionOptions()
<SDKCommentsProvider mentionOptions={mentionOptions}>
<Panel />
</SDKCommentsProvider>Memoize the array, as useMentionOptions does with useMemo. It is context, so a new identity on every render re-renders every comment under the provider.
Typing @ in the composer now opens the picker.
Mark users who may not be mentioned
useUsers returns the users on a resource, not the subset with permission to see a given document. When you know that difference, granted: false renders an option as unavailable rather than hiding it, which is the affordance a permission-aware read will plug into:
// `canSeeDocument` is your app's own permission check.
const mentionOptions = users.map((user) => ({
id: user.sanityUserId,
displayName: user.profile.displayName,
granted: canSeeDocument(user.sanityUserId),
}))Hiding the user instead is also valid, and is a filter on the same array. The difference is whether the author learns that the person exists but cannot be added.
Query for mentions
A mention is an inline object inside a message's Portable Text, carrying a userId and nothing else. Mentions live inside children, not at block level, so a "mentions me" filter has to descend:
count(message[].children[_type == "mention" && userId == $userId]) > 0That filter with useCommentsQuery is a mentions inbox:
import {Comment, useCommentsContext} from '@sanity/collaboration/ui'
import {useCommentsQuery} from '@sanity/sdk-react/collaboration'
export function MentionsInbox() {
const {currentUser} = useCommentsContext()
const {comments} = useCommentsQuery({
filter:
'count(message[].children[_type == "mention" && userId == $userId]) > 0',
params: {userId: currentUser.id},
})
return (
<ul>
{comments.map((comment) => (
<li key={comment.id}>
<p>
{comment.documentType} · {comment.documentId} · {comment.fieldPath}
</p>
<Comment comment={comment} />
</li>
))}
</ul>
)
}It returns comments rather than threads, so the surface renders Comment rather than CommentThread. The query reaches comments across the organization, on documents in any dataset, so each result is labeled with its document type, document ID, and field path. Comment with no callbacks renders no actions, which keeps the inbox read-only: the writes from useCommentActions target the app's own dataset, which a comment from another dataset is not in. To keep the inbox to the app's dataset, add the dataset prefix condition from Scope to one dataset to the filter.
The worked version is an example you can run: mentions.
To find mentions while walking a message yourself, look for inline objects with _type: 'mention' in each block's children. The mentioned user is userId.
Next steps
- How a mention resolves to a name and an avatar: Users and identity.
- Which read to reach for on a cross-document surface: Comment data flow.
- Every helper and type involved: Helpers.
Was this page helpful?
Feedback submitted, thanks for helping us improve the docs. Have you tried these alternatives?