Skip to content

Conversation

@ryanbonial
Copy link
Member

@ryanbonial ryanbonial commented Oct 30, 2025

Description

Added asset management capabilities to the SDK, including new hooks and utilities for working with assets. This PR introduces:

  • Core asset management functions in @sanity/sdk
  • React hooks in @sanity/sdk-react for asset operations
  • A new Assets demo route in the kitchensink app

The implementation provides a complete asset management solution with support for:

  • Querying assets with flexible filtering options
  • Uploading images and files
  • Deleting assets
  • Linking Media Library assets

What to review

  • Core asset functions in packages/core/src/assets/assets.ts
  • React hooks in packages/react/src/hooks/assets/
  • The demo implementation in apps/kitchensink-react/src/routes/AssetsRoute.tsx
  • Public API exports in both packages

Testing

The implementation includes:

  • Type safety with proper overloads for different asset kinds
  • Comprehensive error handling
  • A fully functional demo route that exercises all the new capabilities

Fun gif

assets

@vercel
Copy link

vercel bot commented Oct 30, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
sdk-docs Ready Ready Preview Comment Nov 3, 2025 9:17pm
sdk-kitchensink-react Ready Ready Preview Comment Nov 3, 2025 9:17pm

Copy link
Member

@cngonzalez cngonzalez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like having all these helper hooks! I'm not sure I fully understand why there's a need for a separate store, though. Can we treat image assets like documents?

Also added some comments on maybe using our existing tooling to keep this simple.

@socket-security
Copy link

socket-security bot commented Oct 30, 2025

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​@​sanity/​image-url@​1.2.010010010086100

View full report

@ryanbonial
Copy link
Member Author

@cngonzalez great feedback. I have refactored to use the existing query store and used the sanity-io/image-url package for URL building. Ready for review again.

Copy link
Member

@cngonzalez cngonzalez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few nits but a larger comment:

I really like the addition of the utility hooks but I'm a bit confused about the implementation of useAssets and the underlying getAssetsState et al. I've got two main questions:

  1. We're using the StateSource type but are we actually using a state at any point? (Or the associated subscription / unsubsciption logic). I couldn't see it, but I could be wrong. Should we, to be consistent?
  2. useAssets diverges a bit from the rest of our codebase; I think it's returning whole documents. Should it conform more closely to useDocuments and return handles (we might even be able to pass the buck to getDocumentsState and have simpler logic)? And then we can implement a useAsset that returns the full document that can then be a full doc? Or is that annoying in practice?

// Bump this to force a re-fetch of assets
const [refresh, setRefresh] = useState(0)
const [isUploading, setIsUploading] = useState(false)
const requeryTimeoutsRef = useRef<number[]>([])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious if we still need this after the move to queryStore? Or if this is logic we could already bake in to our assets store? My personal preference is that our usage of hooks in Kitchensink is pretty "vanilla" (mostly for e2e tests to be accurate)

@@ -1,3 +1,4 @@
/* eslint-disable simple-import-sort/exports */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why disable here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, good catch

* Runtime validator and type guard for image asset IDs
* @public
*/
export function isImageAssetId(value: string): value is ImageAssetId {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that importing from the monorepo would cause a dependency loop, so I'm hesitant to add it here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which monorepo do you mean?

/**
* Identifies a specific asset within a Sanity project/dataset.
* Includes optional `projectId`/`dataset` and the required `assetId` (asset document `_id`).
* Useful for cross-instance asset operations without relying on the active ResourceProvider.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I fully understand this comment -- what is meant by cross-instance? I'm also a little wary of the mental model of "this is a document but the _id has a different parameter name"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-instance is across projects and datasets. This change is from Cole's suggestion here: https://sanity-io.slack.com/archives/C07RS3HV1K9/p1761929608902129?thread_ts=1761841748.625049&cid=C07RS3HV1K9

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm the problem, it's me

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the addition of it. It makes it much easier to use assets from difference resources pretty easily.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we are using the active resource provider -- the queryStore is tied to it (that could be my fault -- I pushed you in that direction). I'm just confused on how this behavior differs from other hooks, which I think will also allow a projectId/dataset param to find the right instance.

@ryanbonial
Copy link
Member Author

@cngonzalez

  1. The stateSource is being used for subscribing and unsubscribing using createStateSourceHook in the hook here, so it isn't easy to see explicitly because of the abstraction/generalization layer.
  2. IMO the full asset document that is currently returned is a handle for the actual asset data that lives on the CDN. I'm concerned that adding an assetHandle that is only part of an asset document would be 2 layers of "handles"

@cngonzalez
Copy link
Member

  1. The stateSource is being used for subscribing and unsubscribing using createStateSourceHook in the hook here, so it isn't easy to see explicitly because of the abstraction/generalization layer.

I was multitasking and didn't write carefully 🤦 What I meant was, we're not using the Zustand store state as we do with other utilities. Stores like the documentStore and projectionStore use the bindActionByDataset utility to handle subscription logic and prevent over-fetching.

I actually don't think that assets should have a store or a state; to me, they're just documents. Which brings me to this:

IMO the full asset document that is currently returned is a handle for the actual asset data that lives on the CDN. I'm concerned that adding an assetHandle that is only part of an asset document would be 2 layers of "handles"

I'm mostly wary of having diverging implementations for things that are just documents. The sanity.imageAsset document behaves like a document, is indexed and queried as such, and belong to datasets. It has a pointer to CDN data but also has a ton of metadata, which makes them not small documents -- ref this https://ppsg7ml5.api.sanity.io/v2025-11-05/data/query/test?query=*%5B_type+%3D%3D+%27sanity.imageAsset%27%5D%5B0%5D&perspective=drafts

I would love if we could scale all the efficiencies we worked for (regarding document querying) to the way we work with assets, and I think the easiest way is if we treat the sanity.imageAsset documents as documents.

To be clear, I'm very much in favor of useDeleteAsset, useLinkMediaLibraryAsset, and useUploadAsset; I'm just imagining a world where they take in document handles, do a check to ensure that they're assets, and go about their business.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants