Comments on the organization Comments API and OAuth sessions that stay signed in
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.
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:
useCommentsQueryreads comments that match a GROQ filter across documents, andbuildCommentThreadsgroups 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
addReactionandremoveReaction. - Inline comments anchored to a run of text in a Portable Text field.
updateCommentAnchormoves an anchor without marking the comment as edited. - Comments on a whole document: call
createCommentwithout afieldPath. The Studio's comments inspector lists field comments only, so these don't appear there. - An
errorfield 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
organizationIdon the Sanity config names the organization for apps running outside the Dashboard; organization user lists use it, andAuthBoundarychecks it against each project's organization. It is unrelated toauth.oauth.organizationId.- When the Dashboard removes your app, for example by closing its panel,
ResourceProviderdisposes 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
MessageBusErrorthatemit()orquery()rejects with can includefixanddocsUrlto say how to resolve it.