> 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).

# Read comments

Read the comments on one document with the App SDK, as a list or as threads, and handle loading and disconnected states.

This guide reads the comments on one document, as a flat list or grouped into threads, narrows them by field and status, and handles the three states a live list can be in: loading, switching, and disconnected.

Prerequisites:

- An app with `@sanity/sdk-react` 3.11.0 or later set up. See [Setup](https://www.sanity.io/docs/collaboration/sdk-overview).
- A document handle: a `documentId` and a `documentType`.

## Choose between comments and threads

Two hooks read the same list:

- **useComments** returns every comment on the document, newest first, replies included. It suits a count or a feed.
- **useCommentThreads** returns the same comments grouped into threads, newest thread first. Each thread carries its first comment as `parentComment` and its `replies`, oldest first.

Both take the same options and read the same list, so using them side by side costs one connection, not two.

```tsx
import {useCommentThreads} from '@sanity/sdk-react/collaboration'

function ThreadCount({documentId}: {documentId: string}) {
  const {threads} = useCommentThreads({documentId, documentType: 'article'})

  return <span>{threads.length}</span>
}
```

## Narrow by field and status

`fieldPath` narrows to one field, as a string such as `'title'` or a `Path` array. `status` is `'open'` or `'resolved'`. Leave either out for everything.

```tsx
const {threads} = useCommentThreads({
  documentId,
  documentType: 'article',
  fieldPath: 'title',
  status: 'open',
})
```

The two hooks read these filters slightly differently:

- **Threads** take `status` and `fieldPath` from their first comment, so the filter selects whole threads. A resolved parent brings its replies with it.
- **Comments** have nothing to bring along, so each comment is judged on its own.

`fieldPath: ''` selects the comments on the document as a whole. See [Comment on the whole document](https://www.sanity.io/docs/collaboration/sdk-write-comments).

## Read from drafts, published, or releases

A document can exist as a published document, a draft, and a version per release. `scope` decides which of those the comments are gathered from:

| `scope` | Reads |
| --- | --- |
| `'perspective'` (default) | What you are viewing: a release's own comments, or draft and published pooled together |
| `'exact'` | Only the document ID you passed |
| `'all'` | Every comment on the document, in any form |

To read the pooled draft and published list while a release is in view, pass a non-release `perspective` alongside, such as `perspective: 'drafts'`. To name a release yourself, pass its version ID as the `documentId`.

A content variant is stored as a version document, so under `'perspective'` its comments form a list of their own, the way a release's do. A variant's draft and published comments are separate lists.

## Wrap the read in Suspense

Both hooks suspend until the comments have loaded, so put a `Suspense` boundary above them:

```tsx
import {Suspense} from 'react'

function DocumentComments({documentId}: {documentId: string}) {
  return (
    <Suspense fallback={<p>Loading comments…</p>}>
      <Threads documentId={documentId} />
    </Suspense>
  )
}
```

Switching to a different document or filter happens in a transition instead. The list already on screen stays put and `isPending` goes true until the new one arrives:

```tsx
import {useCommentThreads} from '@sanity/sdk-react/collaboration'

function Threads({documentId}: {documentId: string}) {
  const {threads, isPending} = useCommentThreads({
    documentId,
    documentType: 'article',
  })

  return (
    <ul data-pending={isPending}>
      {threads.map((thread) => (
        <li key={thread.threadId}>{thread.commentsCount} comments</li>
      ))}
    </ul>
  )
}
```

## Show a dropped connection

Comments arrive over a live connection, and that connection can drop, usually because a token expired. A list that has already loaded is not thrown away when that happens. It stays on screen as it last stood, and `error` is set to say it has stopped following the server:

```tsx
import {useCommentThreads} from '@sanity/sdk-react/collaboration'

function Threads({documentId}: {documentId: string}) {
  const {threads, error} = useCommentThreads({
    documentId,
    documentType: 'article',
  })

  return (
    <>
      {error ? <output>Comments are not up to date.</output> : null}
      <ul>
        {threads.map((thread) => (
          <li key={thread.threadId}>{thread.commentsCount} comments</li>
        ))}
      </ul>
    </>
  )
}
```

`error` clears on its own once a connection comes back, which follows the next token refresh.

A connection that fails before the first comments have loaded is different: there is nothing to keep showing, so the hook throws to the nearest error boundary.

## Hide threads on removed fields

Unlike Sanity Studio, the SDK returns every thread it finds. The studio hides threads whose field has left the schema or is hidden by a conditional, which it can do because it has the schema. To do the same, check `fieldPath` against the fields you know about. A nested path such as `body[_key=="a1"].children` starts with its field name and a `[`:

```tsx
import {useCommentThreads} from '@sanity/sdk-react/collaboration'

// The fields your schema has. `''` keeps comments on the whole document.
const knownFields = ['', 'title', 'body']

function VisibleThreads({documentId}: {documentId: string}) {
  const {threads} = useCommentThreads({documentId, documentType: 'article'})
  const visible = threads.filter((thread) =>
    knownFields.some(
      (field) =>
        thread.fieldPath === field || thread.fieldPath.startsWith(`${field}[`),
    ),
  )

  return <span>{visible.length}</span>
}
```

## Next steps

- [Comments hook reference](https://www.sanity.io/docs/collaboration/sdk-overview), for the full signatures of `useComments` and `useCommentThreads`.
- [Query comments](https://www.sanity.io/docs/collaboration/sdk-query-comments), for anything that is not one document's comments.
- [Show comment counts](https://www.sanity.io/docs/collaboration/show-comment-counts), which builds badges from this read.

