How to get referenced document attributes in a custom block serializer
The issue you're experiencing is a common one when working with references in Portable Text. When you query Portable Text that contains custom blocks with references, you only get the _ref ID, not the actual document data. The solution is to dereference the reference in your GROQ query using special syntax for nested arrays.
Since your reference is inside a custom block within your Portable Text array, you need to use GROQ's dereferencing operator (->) combined with array projection syntax.
Solution: Dereference in Your GROQ Query
In your GROQ query where you fetch the Portable Text content, you need to explicitly dereference the reference:
*[_type == "post"][0] {
title,
content[]{
...,
// For your custom video block
_type == "videoBlock" => {
...,
"video": videoReference->{
_id,
title,
"playbackId": asset->playbackId,
"assetId": asset->assetId,
// any other Mux fields you need
}
}
}
}The key parts here are:
videoReference->- The->operator dereferences the reference and fetches the referenced document- You can chain dereferences, like
asset->playbackIdto get nested reference data - Wrap this in a conditional (
_type == "videoBlock" =>) to only apply it to your specific block type
For References in Annotations (markDefs)
If your video reference is actually an annotation rather than a block, the syntax is slightly different because it's nested inside the markDefs array:
content[]{
...,
markDefs[]{
...,
_type == "videoReference" => {
...,
"videoData": @.reference->{
_id,
title,
"playbackId": asset->playbackId,
"assetId": asset->assetId
}
}
}
}The @ symbol refers to the current item in the array context, which is necessary when working with nested arrays like markDefs.
In Your Serializer
Once you've dereferenced in the query, your serializer will receive the full data:
import { PortableText } from '@portabletext/react'
const components = {
types: {
videoBlock: ({value}) => {
// Now value.video contains all the fields you queried
const { playbackId, title } = value.video
return (
<div>
<h3>{title}</h3>
<MuxPlayer playbackId={playbackId} />
</div>
)
}
}
}
// Usage
<PortableText value={content} components={components} />This approach ensures you get all the referenced document data in a single query, avoiding the need to fetch it separately in your render function. The dereferencing happens at query time, so your serializer receives complete data objects rather than just reference IDs.
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.