Querying images used in a document in a schema-agnostic way in Slack thread.
I understand your challenge! You're trying to query all images used in a document in a schema-agnostic way, but *[_type == 'sanity.imageAsset' && references($docId)] isn't working as expected.
The issue is that image assets don't directly reference your documents - it's the other way around. Your documents contain references to image assets (through the asset._ref field in image objects). So the references() function won't work in this direction.
Why your query didn't work
The references() function checks if a document contains a reference TO another document. According to the GROQ documentation, when you use references(), you're asking "which documents reference this ID?"
Image assets (sanity.imageAsset) are stored separately and don't contain references back to documents that use them - they're just storage objects. Your documents reference the assets, not vice versa. That's why your query returns an empty array.
Solutions
Option 1: Query the document and extract image references
You can fetch the document and traverse it to find all image asset references:
*[_id == $docId][0]{
"imageRefs": [
// Direct image fields
mainImage.asset._ref,
// Images in arrays
...images[].asset._ref,
// Images in portable text
...content[_type == "image"].asset._ref,
// Add other known patterns
]
}Then fetch the actual assets:
*[_type == "sanity.imageAsset" && _id in $imageRefs]The challenge is you need to know where images might appear in your schema.
Option 2: Client-side parsing (Recommended for schema-agnostic approach)
For a truly schema-agnostic solution, fetch the entire document and parse it client-side:
function findAllImageRefs(obj, refs = new Set()) {
if (!obj || typeof obj !== 'object') return refs;
// Check if this is an image object with an asset reference
if (obj._type === 'image' && obj.asset?._ref) {
refs.add(obj.asset._ref);
}
// Recursively traverse all properties
Object.values(obj).forEach(value => {
if (Array.isArray(value)) {
value.forEach(item => findAllImageRefs(item, refs));
} else if (typeof value === 'object') {
findAllImageRefs(value, refs);
}
});
return refs;
}
// Usage in your custom input component
const doc = await client.fetch('*[_id == $docId][0]', { docId });
const imageRefs = Array.from(findAllImageRefs(doc));
const assets = await client.fetch(
'*[_type == "sanity.imageAsset" && _id in $refs]',
{ refs: imageRefs }
);This approach:
- Works with any schema structure
- Handles portable text with embedded images
- Finds images in nested arrays and objects
- Requires no schema knowledge
Option 3: Hybrid approach
If you want some GROQ optimization but still need flexibility, you could use GROQ's array flattening with the ellipsis operator:
*[_id == $docId][0]{
...,
"allImageRefs": [
...(*[@ references(^._id)][]...[_type == "image"].asset._ref)
]
}Though this still has limitations with deeply nested structures.
Best recommendation for your use case
Since you're building a custom color input that needs to work across different schemas and handle portable text, Option 2 (client-side parsing) is your best bet. It's truly schema-agnostic, handles all edge cases, and gives you complete control. You can then extract colors from those images to populate your color picker swatches.
Sanity – Build the way you think, not the way your CMS thinks
Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.