@sanity/sdk-react 3.0.0-rc.0 → 3.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/README.md +103 -155
  2. package/dist/index.d.ts +2488 -2019
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +4435 -1529
  5. package/dist/index.js.map +1 -1
  6. package/package.json +55 -59
  7. package/src/_exports/index.ts +2 -4
  8. package/src/_exports/sdk-react.ts +30 -18
  9. package/src/components/SDKProvider.test.tsx +173 -32
  10. package/src/components/SDKProvider.tsx +87 -28
  11. package/src/components/SanityApp.test.tsx +68 -12
  12. package/src/components/SanityApp.tsx +93 -87
  13. package/src/components/auth/AuthBoundary.recovery.test.tsx +86 -0
  14. package/src/components/auth/AuthBoundary.test.tsx +7 -7
  15. package/src/components/auth/AuthBoundary.tsx +29 -6
  16. package/src/components/auth/DashboardAccessRequest.tsx +37 -0
  17. package/src/components/auth/LoginError.test.tsx +191 -5
  18. package/src/components/auth/LoginError.tsx +104 -61
  19. package/src/components/errors/ChunkLoadError.test.tsx +59 -0
  20. package/src/components/errors/ChunkLoadError.tsx +56 -0
  21. package/src/components/errors/CorsErrorComponent.tsx +2 -2
  22. package/src/components/errors/chunkReloadStorage.ts +57 -0
  23. package/src/config/handles.ts +29 -27
  24. package/src/constants.ts +5 -0
  25. package/src/context/ComlinkTokenRefresh.test.tsx +10 -27
  26. package/src/context/ComlinkTokenRefresh.tsx +3 -5
  27. package/src/context/DefaultResourceContext.ts +3 -3
  28. package/src/context/OrganizationResourcesProvider.test.tsx +189 -0
  29. package/src/context/OrganizationResourcesProvider.tsx +111 -0
  30. package/src/context/PerspectiveContext.ts +3 -3
  31. package/src/context/ProjectContext.ts +15 -0
  32. package/src/context/ResourceProvider.test.tsx +93 -90
  33. package/src/context/ResourceProvider.tsx +62 -93
  34. package/src/context/SDKStudioContext.test.tsx +28 -33
  35. package/src/context/SanityInstanceProvider.test.tsx +100 -0
  36. package/src/context/SanityInstanceProvider.tsx +71 -0
  37. package/src/context/WorkbenchTokenRefresh.test.tsx +106 -0
  38. package/src/context/WorkbenchTokenRefresh.tsx +61 -0
  39. package/src/context/renderSanityApp.test.tsx +151 -49
  40. package/src/context/renderSanityApp.tsx +12 -8
  41. package/src/context/workbenchToken.ts +63 -0
  42. package/src/hooks/access/useCheckPermissions.test-d.ts +15 -0
  43. package/src/hooks/access/useCheckPermissions.test.tsx +53 -0
  44. package/src/hooks/access/useCheckPermissions.ts +24 -0
  45. package/src/hooks/agent/agentActions.test.tsx +2 -2
  46. package/src/hooks/agent/agentActions.ts +52 -72
  47. package/src/hooks/agent/useAgentResourceContext.ts +1 -1
  48. package/src/hooks/applications/useApplication.test-d.ts +30 -0
  49. package/src/hooks/applications/useApplication.ts +22 -0
  50. package/src/hooks/applications/useApplications.test-d.ts +31 -0
  51. package/src/hooks/applications/useApplications.ts +25 -0
  52. package/src/hooks/applications/useDeleteApplication.test-d.ts +12 -0
  53. package/src/hooks/applications/useDeleteApplication.ts +11 -0
  54. package/src/hooks/applications/useUpdateApplication.test-d.ts +12 -0
  55. package/src/hooks/applications/useUpdateApplication.ts +11 -0
  56. package/src/hooks/auth/useDashboardOrganizationId.test.tsx +2 -8
  57. package/src/hooks/auth/useVerifyOrgProjects.test.tsx +8 -32
  58. package/src/hooks/auth/useVerifyOrgProjects.tsx +13 -6
  59. package/src/hooks/client/useClient.test.tsx +1 -4
  60. package/src/hooks/client/useClient.ts +1 -0
  61. package/src/hooks/comlink/useFrameConnection.test.tsx +3 -2
  62. package/src/hooks/comlink/useFrameConnection.ts +1 -1
  63. package/src/hooks/comlink/useWindowConnection.test.tsx +4 -3
  64. package/src/hooks/comlink/useWindowConnection.ts +2 -3
  65. package/src/hooks/comments/useCommentActions.test.tsx +150 -0
  66. package/src/hooks/comments/useCommentActions.ts +109 -0
  67. package/src/hooks/comments/useCommentList.ts +79 -0
  68. package/src/hooks/comments/useCommentThreads.test.tsx +107 -0
  69. package/src/hooks/comments/useCommentThreads.ts +73 -0
  70. package/src/hooks/comments/useComments.test.tsx +242 -0
  71. package/src/hooks/comments/useComments.ts +59 -0
  72. package/src/hooks/context/useResource.test.tsx +32 -0
  73. package/src/hooks/context/useResource.ts +24 -0
  74. package/src/hooks/context/useSanityInstance.test.tsx +18 -1
  75. package/src/hooks/context/useSanityInstance.ts +6 -6
  76. package/src/hooks/dashboard/useManageFavorite.test.tsx +110 -46
  77. package/src/hooks/dashboard/useManageFavorite.ts +11 -17
  78. package/src/hooks/dashboard/useNavigateToStudioDocument.test.ts +5 -7
  79. package/src/hooks/dashboard/useNavigateToStudioDocument.ts +2 -6
  80. package/src/hooks/dashboard/useRecordDocumentHistoryEvent.test.ts +0 -2
  81. package/src/hooks/dashboard/useRecordDocumentHistoryEvent.ts +2 -2
  82. package/src/hooks/dashboard/useWindowTitle.test.ts +244 -0
  83. package/src/hooks/dashboard/useWindowTitle.ts +114 -0
  84. package/src/hooks/datasets/useDatasets.test.tsx +116 -0
  85. package/src/hooks/datasets/useDatasets.ts +33 -13
  86. package/src/hooks/document/useApplyDocumentActions.test.tsx +48 -0
  87. package/src/hooks/document/useApplyDocumentActions.ts +66 -48
  88. package/src/hooks/document/useCreateDocument.test.tsx +83 -0
  89. package/src/hooks/document/useCreateDocument.ts +117 -0
  90. package/src/hooks/document/useDocument.test.tsx +37 -8
  91. package/src/hooks/document/useDocument.ts +131 -66
  92. package/src/hooks/document/useDocumentEvent.test.tsx +16 -4
  93. package/src/hooks/document/useDocumentEvent.ts +16 -10
  94. package/src/hooks/document/useDocumentPermissions.test.tsx +8 -7
  95. package/src/hooks/document/useDocumentPermissions.ts +71 -39
  96. package/src/hooks/document/useDocumentSyncStatus.test.ts +1 -0
  97. package/src/hooks/document/useDocumentSyncStatus.ts +13 -9
  98. package/src/hooks/document/useEditDocument.test.tsx +21 -14
  99. package/src/hooks/document/useEditDocument.ts +163 -17
  100. package/src/hooks/documents/useDocuments.test.tsx +64 -44
  101. package/src/hooks/documents/useDocuments.ts +31 -20
  102. package/src/hooks/helpers/createCallbackHook.test.tsx +13 -19
  103. package/src/hooks/helpers/createFetcherHook.test.tsx +180 -0
  104. package/src/hooks/helpers/createFetcherHook.ts +69 -0
  105. package/src/hooks/helpers/createMutationHook.test.tsx +125 -0
  106. package/src/hooks/helpers/createMutationHook.tsx +93 -0
  107. package/src/hooks/helpers/createStateSourceHook.test.tsx +10 -10
  108. package/src/hooks/helpers/createStateSourceHook.tsx +2 -1
  109. package/src/hooks/{document/useApplyDocumentActions.test.ts → helpers/useApplyActions.test.tsx} +92 -53
  110. package/src/hooks/helpers/useApplyActions.ts +68 -0
  111. package/src/hooks/helpers/useNormalizedResourceOptions.test.tsx +226 -0
  112. package/src/hooks/helpers/useNormalizedResourceOptions.ts +79 -48
  113. package/src/hooks/helpers/useResolvedProjectId.test.tsx +59 -0
  114. package/src/hooks/helpers/useResolvedProjectId.ts +35 -0
  115. package/src/hooks/helpers/useTrackHookUsage.ts +37 -0
  116. package/src/hooks/installations/useInstallation.test-d.ts +19 -0
  117. package/src/hooks/installations/useInstallation.ts +22 -0
  118. package/src/hooks/installations/useInstallations.test-d.ts +26 -0
  119. package/src/hooks/installations/useInstallations.ts +25 -0
  120. package/src/hooks/organizations/useOrganization.test-d.ts +60 -0
  121. package/src/hooks/organizations/useOrganization.test.ts +63 -0
  122. package/src/hooks/organizations/useOrganization.ts +34 -0
  123. package/src/hooks/organizations/useOrganizations.test-d.ts +68 -0
  124. package/src/hooks/organizations/useOrganizations.test.ts +63 -0
  125. package/src/hooks/organizations/useOrganizations.ts +39 -0
  126. package/src/hooks/paginatedDocuments/usePaginatedDocuments.test.tsx +34 -27
  127. package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +35 -22
  128. package/src/hooks/presence/usePresence.test.tsx +0 -15
  129. package/src/hooks/presence/usePresence.ts +22 -20
  130. package/src/hooks/presence/usePresenceForDocument.test.tsx +141 -0
  131. package/src/hooks/presence/usePresenceForDocument.ts +104 -0
  132. package/src/hooks/presence/useReportPresence.test.tsx +202 -0
  133. package/src/hooks/presence/useReportPresence.ts +170 -0
  134. package/src/hooks/preview/useDocumentPreview.test.tsx +0 -1
  135. package/src/hooks/preview/useDocumentPreview.tsx +12 -14
  136. package/src/hooks/projection/useDocumentProjection.test.tsx +78 -9
  137. package/src/hooks/projection/useDocumentProjection.ts +81 -2
  138. package/src/hooks/projects/useProject.test-d.ts +56 -0
  139. package/src/hooks/projects/useProject.test.tsx +120 -0
  140. package/src/hooks/projects/useProject.ts +42 -43
  141. package/src/hooks/projects/useProjects.test-d.ts +60 -0
  142. package/src/hooks/projects/useProjects.test.ts +45 -98
  143. package/src/hooks/projects/useProjects.ts +17 -32
  144. package/src/hooks/query/useQuery.test.tsx +6 -5
  145. package/src/hooks/query/useQuery.ts +94 -17
  146. package/src/hooks/releases/useActiveReleases.test.tsx +13 -7
  147. package/src/hooks/releases/useActiveReleases.ts +28 -24
  148. package/src/hooks/releases/useAllReleases.test.tsx +93 -0
  149. package/src/hooks/releases/useAllReleases.ts +62 -0
  150. package/src/hooks/releases/useApplyReleaseActions.test.tsx +66 -0
  151. package/src/hooks/releases/useApplyReleaseActions.ts +82 -0
  152. package/src/hooks/releases/usePerspective.test.tsx +18 -10
  153. package/src/hooks/releases/usePerspective.ts +13 -17
  154. package/src/hooks/users/useUser.test.tsx +3 -9
  155. package/src/hooks/users/useUser.ts +4 -8
  156. package/src/hooks/users/useUsers.test.tsx +103 -7
  157. package/src/hooks/users/useUsers.ts +38 -5
  158. package/src/utils/resolveOrgResources.test.ts +111 -0
  159. package/src/utils/resolveOrgResources.ts +69 -0
  160. package/src/hooks/context/useDefaultResource.test.tsx +0 -25
  161. package/src/hooks/context/useDefaultResource.ts +0 -30
  162. package/src/hooks/dashboard/useDispatchIntent.test.ts +0 -254
  163. package/src/hooks/dashboard/useDispatchIntent.ts +0 -157
  164. package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.test.ts +0 -107
  165. package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.ts +0 -39
  166. package/src/hooks/datasets/useDatasets.test.ts +0 -66
  167. package/src/hooks/helpers/useNormalizedResourceOptions.test.ts +0 -65
  168. package/src/hooks/projects/useProject.test.ts +0 -79
package/README.md CHANGED
@@ -5,9 +5,7 @@
5
5
  <h1 align="center">Sanity App SDK (React)</h1>
6
6
  </p>
7
7
 
8
- React hooks for creating Sanity applications. Live by default, optimistic updates, multi-resource support.
9
-
10
- > **Requires React 19** — `react` and `react-dom` `^19.2.0` are the minimum peer dependencies.
8
+ React hooks for creating Sanity applications. Live by default, optimistic updates, multi-project support.
11
9
 
12
10
  ---
13
11
 
@@ -32,31 +30,22 @@ Opens at `https://www.sanity.io/welcome?dev=http%3A%2F%2Flocalhost%3A3333`, prox
32
30
  ### 2. Project configuration
33
31
 
34
32
  ```tsx
35
- import {SanityApp} from '@sanity/sdk-react'
33
+ import {SanityApp, type SanityConfig} from '@sanity/sdk-react'
34
+
35
+ const config: SanityConfig[] = [
36
+ {projectId: 'abc123', dataset: 'production'},
37
+ {projectId: 'def456', dataset: 'production'}, // multi-project support
38
+ ]
36
39
 
37
40
  export function App() {
38
41
  return (
39
- <SanityApp
40
- resources={{
41
- 'default': {projectId: 'abc123', dataset: 'production'},
42
- 'second-project': {projectId: 'def456', dataset: 'production'},
43
- }}
44
- fallback={<div>Loading...</div>}
45
- >
42
+ <SanityApp config={config} fallback={<div>Loading...</div>}>
46
43
  <YourApp />
47
44
  </SanityApp>
48
45
  )
49
46
  }
50
47
  ```
51
48
 
52
- In Sanity, a **resource** identifies where your data lives. It can be one of:
53
-
54
- - a project and dataset pair (`{ projectId, dataset }`)
55
- - a media library (`{ mediaLibraryId }`)
56
- - or a canvas (`{ canvasId }`)
57
-
58
- The `resources` prop is a map of named resources. Each resource tells the SDK where to read and write data. The resource keyed `"default"` is used automatically when no explicit resource is specified in a hook.
59
-
60
49
  **Auth is automatic** — Dashboard injects an auth token via iframe. No custom login flow is needed for your application.
61
50
 
62
51
  ---
@@ -71,14 +60,11 @@ Document handles are a core concept for apps built with the App SDK. Document ha
71
60
  type DocumentHandle = {
72
61
  documentId: string
73
62
  documentType: string
74
- resource: DocumentResource // e.g. { projectId, dataset }, { mediaLibraryId }, or { canvasId }
63
+ projectId?: string // optional if using the default projectId or inside a ResourceProvider
64
+ dataset?: string // optional if using the default dataset or inside a ResourceProvider
75
65
  }
76
66
  ```
77
67
 
78
- The `resource` field identifies where the document lives. When you fetch document handles from hooks like `useDocuments`, the `resource` is automatically populated from the current context.
79
-
80
- Most hooks also accept `resourceName` to target a specific named resource declared in the `resources` prop on `<SanityApp>`, or a `resource` object directly. When neither is provided, the `"default"` resource is used.
81
-
82
68
  **Best practice:** Fetch document handles first → pass them to child components → fetch individual document content from child components.
83
69
 
84
70
  ---
@@ -122,6 +108,10 @@ const {data} = useQuery({
122
108
  #### Document Manipulation
123
109
 
124
110
  ```tsx
111
+ // Create a new document and get back its handle
112
+ const createArticle = useCreateDocument({documentType: 'article'})
113
+ const newHandle = await createArticle({title: 'Untitled', status: 'draft'})
114
+
125
115
  // Edit field (emits optimistic updates to useEditDocument listeners, creates a draft automatically)
126
116
  const editTitle = useEditDocument({...handle, path: 'title'})
127
117
  editTitle('New Title') // fires on every keystroke, debounced internally
@@ -137,7 +127,7 @@ import {
137
127
  unpublishDocument,
138
128
  deleteDocument,
139
129
  createDocument,
140
- discardDocument,
130
+ discardDraft,
141
131
  } from '@sanity/sdk-react'
142
132
 
143
133
  const apply = useApplyDocumentActions()
@@ -147,13 +137,6 @@ await apply(publishDocument(handle))
147
137
 
148
138
  // Batch actions
149
139
  await apply([publishDocument(handle1), publishDocument(handle2), deleteDocument(handle3)])
150
-
151
- // Create new document with an optional initial content
152
- const newHandle = createDocumentHandle({
153
- documentId: crypto.randomUUID(),
154
- documentType: 'article',
155
- })
156
- await apply(createDocument(newHandle, {title: 'Untitled', status: 'draft'}))
157
140
  ```
158
141
 
159
142
  #### Events & Permissions
@@ -163,7 +146,7 @@ await apply(createDocument(newHandle, {title: 'Untitled', status: 'draft'}))
163
146
  useDocumentEvent({
164
147
  ...handle,
165
148
  onEvent: (event) => {
166
- // event.type: 'documentEdited' | 'documentPublished' | 'documentDeleted' | ...
149
+ // event.type: 'edited' | 'published' | 'deleted' | 'created' | 'unpublished' | 'discarded' | ...
167
150
  console.log(event.type, event.documentId)
168
151
  },
169
152
  })
@@ -193,45 +176,37 @@ The `useApplyDocumentActions` hook is used to perform document lifecycle operati
193
176
  | `publishDocument` | Publish a draft (copy draft → published) |
194
177
  | `unpublishDocument` | Unpublish (delete published, keep draft) |
195
178
  | `deleteDocument` | Delete document entirely (draft and published) |
196
- | `discardDocument` | Discard draft changes, revert to published |
179
+ | `discardDraft` | Discard draft changes, revert to published |
197
180
 
198
181
  #### Creating Documents
199
182
 
200
- To create a document, you must:
201
-
202
- 1. Generate your own document ID (using `crypto.randomUUID()`)
203
- 2. Create a document handle with `createDocumentHandle`
204
- 3. Apply the `createDocument` action using the document handle, along with optional initial content
183
+ `useCreateDocument` handles the common case: it generates the document ID for you and returns the new document's handle, ready to pass to `useDocument`, `useEditDocument`, or your router.
205
184
 
206
185
  ```tsx
207
- import {useApplyDocumentActions, createDocumentHandle, createDocument} from '@sanity/sdk-react'
186
+ import {useCreateDocument} from '@sanity/sdk-react'
208
187
 
209
188
  function CreateArticleButton() {
210
- const apply = useApplyDocumentActions()
189
+ const createArticle = useCreateDocument({documentType: 'article'})
211
190
 
212
- const handleCreateArticle = () => {
213
- const newId = crypto.randomUUID()
214
- const handle = createDocumentHandle({
215
- documentId: newId,
216
- documentType: 'article',
191
+ const handleCreateArticle = async () => {
192
+ const handle = await createArticle({
193
+ title: 'New Article',
194
+ status: 'draft',
195
+ author: {_type: 'reference', _ref: 'author-123'},
217
196
  })
218
197
 
219
- apply(
220
- createDocument(handle, {
221
- title: 'New Article',
222
- status: 'draft',
223
- author: {_type: 'reference', _ref: 'author-123'},
224
- }),
225
- )
226
-
227
- // Navigate to the new document
228
- navigate(`/articles/${newId}`)
198
+ // Navigate to the new document using the returned handle
199
+ navigate(`/articles/${handle.documentId}`)
229
200
  }
230
201
 
231
202
  return <button onClick={handleCreateArticle}>Create Article</button>
232
203
  }
233
204
  ```
234
205
 
206
+ To use a specific ID instead of a generated one, pass it on the handle (`useCreateDocument({documentType: 'article', documentId})`) or per call (`createArticle(initialValue, {documentId})`).
207
+
208
+ For atomic create-and-publish, or to create several documents in a single transaction, drop down to `useApplyDocumentActions` with the `createDocument` and `publishDocument` action creators (see [Batch Operations](#batch-operations) below).
209
+
235
210
  #### Publishing Documents
236
211
 
237
212
  ```tsx
@@ -322,6 +297,27 @@ function ArticleList() {
322
297
 
323
298
  ---
324
299
 
300
+ ### Viewport-Based Lazy Loading
301
+
302
+ Pass a `ref` to `useDocumentProjection` to fetch only when the element enters the viewport. Wrap the component in Suspense like any other data hook:
303
+
304
+ ```tsx
305
+ import {useRef} from 'react'
306
+
307
+ function LazyArticleCard({handle}: {handle: DocumentHandle}) {
308
+ const ref = useRef<HTMLDivElement>(null)
309
+ const {data} = useDocumentProjection({
310
+ ...handle,
311
+ ref, // Only fetches when element is visible
312
+ projection: '{ title }',
313
+ })
314
+
315
+ return <div ref={ref}>{data?.title}</div>
316
+ }
317
+ ```
318
+
319
+ ---
320
+
325
321
  ### Draft/Published Model
326
322
 
327
323
  Sanity has two document states:
@@ -334,7 +330,7 @@ The SDK handles updating the document state automatically:
334
330
  - `useDocument()` returns draft if exists, else published
335
331
  - `useEditDocument()` creates draft on first edit (automatic)
336
332
  - `publishDocument()` copies draft → published, deletes draft
337
- - `discardDocument()` deletes draft, reverts to published
333
+ - `discardDraft()` deletes draft, reverts to published
338
334
 
339
335
  #### LiveEdit Documents
340
336
 
@@ -344,7 +340,6 @@ For documents that don't need the draft/published workflow (such as settings, co
344
340
  const settingsHandle: DocumentHandle = {
345
341
  documentId: 'site-settings',
346
342
  documentType: 'settings',
347
- resource: {projectId: 'abc123', dataset: 'production'},
348
343
  liveEdit: true, // Edits apply directly without creating a draft
349
344
  }
350
345
 
@@ -356,7 +351,7 @@ const editSettings = useEditDocument(settingsHandle)
356
351
 
357
352
  - Drafts will not be created when the document is edited
358
353
  - Edits will be applied directly to the published document
359
- - `publishDocument()`, `unpublishDocument()`, and `discardDocument()` actions cannot be used (since liveEdit documents are always published and do not have drafts)
354
+ - `publishDocument()`, `unpublishDocument()`, and `discardDraft()` actions cannot be used (since liveEdit documents are always published and do not have drafts)
360
355
 
361
356
  For more details, see the [Sanity documentation on liveEdit documents](https://www.sanity.io/docs/content-lake/drafts).
362
357
 
@@ -376,80 +371,32 @@ Any mutation to a subscribed document (even fields you don't display) will trigg
376
371
 
377
372
  ---
378
373
 
379
- ### Multi-Resource Access
374
+ ### Multi-Project Access
380
375
 
381
- If your app only uses a single project and dataset, the `"default"` resource handles everything. When you need to pull data from additional projects, datasets, media libraries, or canvases, you can specify additional resources as needed. There are three main approaches to providing additional resources:
376
+ The SDK supports accessing documents from multiple projects and datasets simultaneously. There are two main approaches:
382
377
 
383
- #### Approach 1: Use Named Resources (recommended)
378
+ #### Approach 1: Specify Project/Dataset Directly in the Handle
384
379
 
385
- Register all your resources in the `resources` prop on `<SanityApp>` and reference them by name with `resourceName`:
386
-
387
- ```tsx
388
- import {SanityApp} from '@sanity/sdk-react'
389
-
390
- export function App() {
391
- return (
392
- <SanityApp
393
- resources={{
394
- default: {projectId: 'project-a', dataset: 'production'},
395
- staging: {projectId: 'project-a', dataset: 'staging'},
396
- media: {mediaLibraryId: 'my-media-library'},
397
- }}
398
- fallback={<div>Loading...</div>}
399
- >
400
- <MyApp />
401
- </SanityApp>
402
- )
403
- }
404
- ```
405
-
406
- Then reference them in hooks:
407
-
408
- ```tsx
409
- import {useDocument, useQuery} from '@sanity/sdk-react'
410
-
411
- function StagingPreview({documentId}: {documentId: string}) {
412
- const {data} = useDocument({
413
- documentId,
414
- documentType: 'article',
415
- resourceName: 'staging',
416
- })
417
- return <pre>{JSON.stringify(data, null, 2)}</pre>
418
- }
419
-
420
- function MediaAssets() {
421
- const {data} = useQuery({
422
- query: '*[_type == "sanity.asset"][0...10]',
423
- resourceName: 'media',
424
- })
425
- return (
426
- <ul>
427
- {data?.map((asset) => (
428
- <li key={asset._id}>{asset.originalFilename}</li>
429
- ))}
430
- </ul>
431
- )
432
- }
433
- ```
434
-
435
- #### Approach 2: Pass a Resource Object Directly
436
-
437
- If you only need to access a resource in one or two places within your app, you can pass it inline via the `resource` option:
380
+ Pass `projectId` and `dataset` directly in document handles to fetch data from specific projects (note that any `projectId` and `dataset` pair you pass must be defined in your application’s array of [SanityConfig objects](https://www.sanity.io/docs/app-sdk/sdk-configuration#d95b8773097c)):
438
381
 
439
382
  ```tsx
440
383
  import {useDocument} from '@sanity/sdk-react'
441
384
 
442
385
  function MultiProjectComponent() {
386
+ // Fetch from Project A
443
387
  const {data: productA} = useDocument({
444
388
  documentId: 'product-123',
445
389
  documentType: 'product',
446
- resource: {projectId: 'project-a', dataset: 'production'},
390
+ projectId: 'project-a',
391
+ dataset: 'production',
447
392
  })
448
393
 
394
+ // Fetch from Project B
449
395
  const {data: productB} = useDocument({
450
396
  documentId: 'product-456',
451
397
  documentType: 'product',
452
- resource: {projectId: 'project-b', dataset: 'staging'},
398
+ projectId: 'project-b',
399
+ dataset: 'staging',
453
400
  })
454
401
 
455
402
  return (
@@ -461,41 +408,46 @@ function MultiProjectComponent() {
461
408
  }
462
409
  ```
463
410
 
464
- #### Approach 3: Use ResourceProvider to Set Context
411
+ #### Approach 2: Use ResourceProvider to Set Context
465
412
 
466
- If you need to access certain resources within multiple sibling components, wrap those components in `ResourceProvider` and set a default resource for all its child components:
413
+ Wrap components in `ResourceProvider` to set default project/dataset values for all child components:
467
414
 
468
415
  ```tsx
469
- import {ResourceProvider, useDocument} from '@sanity/sdk-react'
416
+ // App.tsx
417
+ import {ResourceProvider, useDocument, useSanityInstance} from '@sanity/sdk-react'
470
418
 
471
419
  function ProductCard({productId}: {productId: string}) {
472
- // No need to specify a resource - inherited from ResourceProvider
420
+ // Get the current project/dataset from context
421
+ const {config} = useSanityInstance()
422
+
423
+ // No need to specify projectId/dataset - inherited from ResourceProvider
473
424
  const {data: product} = useDocument({
474
425
  documentId: productId,
475
426
  documentType: 'product',
476
427
  })
477
428
 
478
- return <h3>{product?.title}</h3>
429
+ return (
430
+ <div>
431
+ <h3>{product?.title}</h3>
432
+ <p>
433
+ From: {config.projectId}.{config.dataset}
434
+ </p>
435
+ </div>
436
+ )
479
437
  }
480
438
 
481
439
  export function MultiProjectApp() {
482
440
  return (
483
441
  <div>
484
442
  {/* Products from Project A */}
485
- <ResourceProvider
486
- resource={{projectId: 'project-a', dataset: 'production'}}
487
- fallback={<div>Loading...</div>}
488
- >
443
+ <ResourceProvider projectId="project-a" dataset="production" fallback={<div>Loading...</div>}>
489
444
  <h2>Project A Products</h2>
490
445
  <ProductCard productId="product-123" />
491
446
  <ProductCard productId="product-456" />
492
447
  </ResourceProvider>
493
448
 
494
449
  {/* Products from Project B */}
495
- <ResourceProvider
496
- resource={{projectId: 'project-b', dataset: 'staging'}}
497
- fallback={<div>Loading...</div>}
498
- >
450
+ <ResourceProvider projectId="project-b" dataset="staging" fallback={<div>Loading...</div>}>
499
451
  <h2>Project B Products</h2>
500
452
  <ProductCard productId="product-789" />
501
453
  </ResourceProvider>
@@ -506,16 +458,17 @@ export function MultiProjectApp() {
506
458
 
507
459
  **Key Points:**
508
460
 
509
- - The `"default"` resource is used automatically when no `resourceName`, `resource`, or `ResourceProvider` context is present
510
- - Named resources (`resourceName`) are the recommended pattern for apps that work with multiple data sources
511
- - `ResourceProvider` sets context for an entire subtree — hooks inside it inherit the resource without needing to specify it
512
- - You can nest `ResourceProvider` components; the closest provider wins when no explicit resource is given
461
+ - When using hooks that take document handles as arguments (such useDocument, useEditDocument, useQuery, etc.), the document handles’ `projectId` and `dataset` values can be explicitly set to fetch documents from arbitrary projects and datasets
462
+ - The ResourceProvider component is used to create a project ID and dataset context that child components will inherit from; this can negate the need to specify the project ID and dataset values for document handles in hooks called by child components
463
+ - Use `useSanityInstance()` to access the context configuration for the current component: `const {config} = useSanityInstance()`
464
+ - You can nest ResourceProvider components to create component trees with different project/dataset configurations — but be aware that, when the project ID and dataset values for document handles are _not_ specified, the project ID and dataset from the closest ResourceProvider context will be used
465
+ - Regardless of the approach you use, the project IDs and dataset names you reference (whether in document handles or ResourceProviders) must be enumerated in your application’s [SanityConfig objects](https://www.sanity.io/docs/app-sdk/sdk-configuration#d95b8773097c)
513
466
 
514
467
  ---
515
468
 
516
469
  ### Using the SDK inside Sanity Studio
517
470
 
518
- The SDK can be embedded directly inside a Sanity Studio with zero manual configuration. Sanity Studio provides `SDKStudioContext` automatically, so `SanityApp` derives its resource configuration and auth from the Studio's workspace without any setup.
471
+ The SDK can be embedded directly inside a Sanity Studio with zero manual configuration. Sanity Studio provides `SDKStudioContext` automatically, so `SanityApp` derives `projectId`, `dataset`, and auth from the Studio's workspace without any setup.
519
472
 
520
473
  #### Zero-config setup (recommended)
521
474
 
@@ -549,13 +502,11 @@ function StudioSDKWrapper({children}) {
549
502
 
550
503
  #### Explicit config takes precedence
551
504
 
552
- If you pass `resources` (data sources) or `config` (auth, studio, and perspective settings) props to `SanityApp`, they take precedence over any workspace config picked up by `SDKStudioContext`:
505
+ If you pass a `config` prop to `SanityApp`, this config will take precedence over any workspace config picked up by `SDKStudioContext`:
553
506
 
554
507
  ```tsx
555
- <SanityApp
556
- resources={{default: {projectId: 'other-project', dataset: 'staging'}}}
557
- fallback={<Loading />}
558
- >
508
+ // This uses the explicit config, not the Studio workspace
509
+ <SanityApp config={{projectId: 'other-project', dataset: 'staging'}} fallback={<Loading />}>
559
510
  <MyComponent />
560
511
  </SanityApp>
561
512
  ```
@@ -566,35 +517,32 @@ If the Studio provides a reactive token source via `workspace.auth.token`, the S
566
517
 
567
518
  For older Studios that don't expose a token source, the SDK falls back to discovering the auth token from `localStorage` or cookie auth.
568
519
 
569
- #### `studioMode` has been removed
520
+ #### Migrating from `studioMode`
570
521
 
571
- The `studioMode` config field was removed in v3. The recommended replacement is to use the zero-config `SDKStudioContext` approach described above — which requires no `config` prop at all.
522
+ The `studioMode` config field is deprecated. If you are currently using it, the recommended replacement is to use the zero-config `SDKStudioContext` approach described above — which requires no `config` prop at all.
572
523
 
573
- If you previously used `studioMode`, replace it with `studio`:
524
+ If you need to pass an explicit config, replace `studioMode` with `studio`:
574
525
 
575
526
  ```diff
576
527
  const config: SanityConfig = {
577
- - projectId: 'my-project',
578
- - dataset: 'production',
528
+ projectId: 'my-project',
529
+ dataset: 'production',
579
530
  - studioMode: { enabled: true },
580
531
  + studio: {},
581
532
  }
582
533
  ```
583
534
 
584
- See the [Migration Guide](./guides/0-Migration-Guide.md) for all v3 breaking changes and upgrade steps.
585
-
586
535
  ---
587
536
 
588
- ### TypeScript
537
+ ### TypeScript & TypeGen
589
538
 
590
- ```tsx
591
- import {type SanityDocument} from '@sanity/sdk-react'
539
+ ```bash
540
+ # Generate types from your schema
541
+ npx sanity typegen generate
542
+ ```
592
543
 
593
- interface Article extends SanityDocument {
594
- _type: 'article'
595
- title: string
596
- body: string
597
- }
544
+ ```tsx
545
+ import type {Article} from './sanity.types'
598
546
 
599
547
  const {data} = useDocument<Article>(handle)
600
548
  // data is typed as Article