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

Published: October 29, 2026

v3.11.0

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.

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 lists the other changes, and the collaboration guide 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.

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.
Sanity logosanity.ioAll Systems Operational© Sanity 2026
Change site theme