# Comments on the organization Comments API and OAuth sessions that stay signed in

**Version:** v3.11.0

**Published:** October 29, 2026

This release adds beta comment hooks that read and write the same organization-level comments the Studio uses, and keeps OAuth sessions signed in by refreshing the access token before it expires. No code changes are needed. If your OAuth app reads the token from `client.config().token`, switch to `await client.getAuth()`.

## Comments backed by the organization Comments API (beta)

`@sanity/sdk-react/collaboration` exports comment hooks backed by the organization Comments API, the store the Studio reads. They keep the names of the comment hooks on the package root: `useComments`, `useCommentThreads`, and `useCommentActions`. The framework-agnostic functions are in `@sanity/sdk/collaboration`. Comments use the organization that owns the project, so there is nothing to configure.

**TitleThreads.tsx**

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

export function TitleThreads({documentId}: {documentId: string}) {
  const {threads} = useCommentThreads({
    documentId,
    documentType: 'article',
    fieldPath: 'title',
    status: 'open',
  })
  const {setCommentStatus} = useCommentActions()

  return (
    <ul>
      {threads.map((thread) => (
        <li key={thread.parentComment.id}>
          <button
            onClick={() => setCommentStatus({commentId: thread.parentComment.id, status: 'resolved'})}
          >
            Resolve
          </button>
        </li>
      ))}
    </ul>
  )
}
```

The collaboration hooks also add:

- `useCommentsQuery` reads comments that match a GROQ filter across documents, and `buildCommentThreads` groups its flat result into threads. The filter runs across the whole organization, so add a condition to it if your query should stay on one dataset.
- Reactions, with `addReaction` and `removeReaction`.
- Inline comments anchored to a run of text in a Portable Text field. `updateCommentAnchor` moves an anchor without marking the comment as edited.
- Comments on a whole document: call `createComment` without a `fieldPath`. The Studio's comments inspector lists field comments only, so these don't appear there.
- An `error` field on read results. It is set when the live connection drops after comments have loaded, and the list stays on screen as it last stood.

The comment hooks on the `@sanity/sdk-react` root read the comments add-on dataset. They are deprecated and will be removed in the next major. Don't switch until your Studio reads the Comments API and your existing comments are migrated, or your app and your Studio will show different comments. Moving over takes more than a new import; the [migration guide](https://github.com/sanity-io/sdk/blob/main/packages/react/guides/0-Migration-Guide.md) lists the other changes, and the [collaboration guide](https://github.com/sanity-io/sdk/blob/main/packages/react/guides/Collaboration.md) covers every hook and action.

## OAuth sessions stay signed in

In OAuth apps, `@sanity/sdk` now refreshes the access token shortly before it expires. Requests made during a refresh wait for the new token instead of failing with a 401. A refresh no longer restarts listeners or refetches documents and queries: listeners resume where they left off. Tabs on the same origin take turns refreshing, so one tab never uses a refresh token another tab just rotated.

Clients in OAuth apps now follow the current token instead of being recreated on each refresh, so `client.config().token` no longer holds it. Read it with `await client.getAuth()` instead. Other auth modes keep a fixed token on each client, so `client.config().token` still works there.

**useCurrentToken.ts**

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

export function useCurrentToken() {
  const client = useClient({apiVersion: '2025-10-29'})
  return async () => {
    const {token} = await client.getAuth()
    return token
  }
}
```

## 🐛 Notable bugfixes and improvements

- `organizationId` on the Sanity config names the organization for apps running outside the Dashboard; organization user lists use it, and `AuthBoundary` checks it against each project's organization. It is unrelated to `auth.oauth.organizationId`.
- When the Dashboard removes your app, for example by closing its panel, `ResourceProvider` disposes its Sanity instance and closes the app's message bus connection, while an app the Dashboard only hides keeps both.
- An SDK call that reaches the message bus after the Dashboard removed your app now throws instead of opening a connection that never closes, so stop timers and pending calls when your app unmounts.
- When the Dashboard refuses a message from your app, the `MessageBusError` that `emit()` or `query()` rejects with can include `fix` and `docsUrl` to say how to resolve it.

