> For AI agents: the complete Sanity documentation index is available at [https://www.sanity.io/docs/llms.txt](https://www.sanity.io/docs/llms.txt).

# Users and identity

Why comments store user IDs, how names and avatars are looked up at render time, and where the user directory comes from.

Comments store user IDs, not names. Every name and avatar on a comments surface is looked up at render time through one function on the provider. That makes a user directory the one thing no comments surface can do without. This page explains where that directory comes from, and why the lookup has the shape it does.

## Where the current user and user lookup come from

`SDKCommentsProvider` is the one to reach for on a Sanity app. It is `CommentsProvider` with the two identity answers filled in from the App SDK: the current user from the auth store, and everybody else from the user store, an ID at a time as the comments ask for them. So on a Sanity project this is the whole of it:

```tsx
<Suspense fallback={<p>Loading…</p>}>
  <SDKCommentsProvider>
    <Panel />
  </SDKCommentsProvider>
</Suspense>
```

It suspends until the current user resolves, the way the SDK's own reads do, so the wait is a Suspense boundary above it rather than a prop. A viewer who is logged out, or whose sign-in failed, throws to the nearest error boundary instead: there is no anonymous mode to fall back to, so waiting would never end. Keep it inside your app's auth boundary, or reach for `CommentsProvider` and answer for the viewer yourself.

`CommentsProvider` is the same provider with those two answers supplied by you:

```tsx
<CommentsProvider currentUser={me} getUser={(id) => usersById.get(id) ?? null}>
  <Panel />
</CommentsProvider>
```

Reach for `CommentsProvider` when:

- **The users are already in hand.** A panel with a mention picker has called `useUsers` for its options, so the directory is in memory already. The SDK-backed lookup does not know that, and subscribes per author ID anyway.
- **You know the viewer without asking.** From the server or your own session, so the panel renders on the first paint rather than flashing a fallback while identity resolves.
- **You are rendering in a test or a harness.** A fixture user and a `Map` are the whole of a lookup, and no part of the panel needs the SDK to be there.
- **The names are not the Sanity ones.** Localized display names, your own avatar CDN, or a shared account that should read "Support".
- **The viewer is not a Sanity user at all.** This is a public or customer-facing surface, and the only case where the SDK-wired variant has nothing to resolve.

Everything under either one is identical. The panel does not know which of the two is above it.

`currentUser` is required, so render a fallback until you have it rather than passing a placeholder. A placeholder ID gets the "you" labels and the default edit and delete capabilities wrong.

## The `getUser` function is synchronous and has three answers

The components need a name while they render, so `getUser` answers immediately:

```typescript
type GetUser = (userId: string) => CommentUser | null | undefined
```

| Answer | Means | Renders |
| --- | --- | --- |
| a user | resolved | the avatar and the display name |
| `undefined` | still being fetched | a skeleton |
| `null` | there is no such one | the empty avatar, and "Unknown" |

The two unresolved answers are worth keeping apart. A lookup that returns `undefined` for a deleted author leaves that comment showing a skeleton forever, since nothing is coming. Adding `?? null` is usually the whole fix.

When a pending user arrives, return a new function identity. That is what re-renders the skeletons into names.

This is the part that cannot be designed away. A message embeds mention IDs inside Portable Text and a reaction carries a bare user ID, so the data cannot be enriched up front without rewriting it.

`useCommentUser` is the same lookup, in the shape the primitives that show a user take:

```tsx
const {user, loading} = useCommentUser(comment.authorId)

<UserAvatar user={user} loading={loading} />
```

A user is `loading` only while the lookup answers `undefined`. Once it answers `null`, this settles with no user and `loading: false`, which is what renders "Unknown" rather than a skeleton that never resolves.

## The system user

One ID is answered for you. `<system>`, the author of comments Sanity writes itself, always resolves to "Sanity" with the Sanity logo, whatever your lookup returns. Your directory never has to know about it.

## What a mention carries

A mention is an inline object inside a message's Portable Text, and it carries a `userId` and nothing else:

```typescript
{_type: 'mention', _key: 'm1', userId: 'user-def'}
```

So rendering a name means looking the user up, through the same three-answer lookup as an author. `CommentMessageContent` renders a mention as a `MentionChip`: the display name, prefixed with `@`, and suffixed with "(you)" when it is the current user. Unlike an avatar or a name, the chip has no loading state: until the lookup returns a user, it shows the raw user ID.

Which users may be mentioned is a permissions question rather than an identity one, and it is answered by the options you pass the provider. See [Enable mentions](https://www.sanity.io/docs/collaboration/enable-mentions).

## Reactions carry user IDs too

Each reaction carries a bare user ID, so the pill tooltips go through the same lookup to name who reacted. When the lookup has no display name for a reactor, the tooltip shows the raw user ID instead, as a mention chip does. The picker offers six shortnames, the set the API accepts, and the provider's `reactionOptions` prop can narrow or reorder them.

Rendering is not narrowed by that set. A reaction whose shortname the package does not know renders as its own text rather than disappearing.

## Where to go next

- To turn the mention picker on: [Enable mentions](https://www.sanity.io/docs/collaboration/enable-mentions).
- Both providers' props, and how capabilities resolve: [Providers](https://collaboration-platform.sanity.dev/?path=/docs/default-providers--docs).
- Which primitives take a user and which read the provider: [Comment components](https://www.sanity.io/docs/collaboration/components).

