GROQ: Accessing file URLs within block content arrays
Yes, your approach is almost correct! When you have a file asset embedded in block content (Portable Text), you need to resolve the asset reference to get its URL. Your query structure is on the right track:
"body": body[]{
...,
_type == 'file' => {
...,
"documentUrl": asset->url
}
}However, there are a couple of important considerations based on the discussion from the Sanity community:
Performance optimization: If you need multiple fields from the asset (like size, original filename, etc.), avoid using multiple -> references for the same document. Instead, merge them into a single subquery:
"body": body[]{
...,
_type == 'file' => {
...,
...(asset-> {
"documentUrl": url,
"documentSize": size,
"documentOriginalFilename": originalFilename
})
}
}Alternative approach using @sanity/asset-utils: Instead of resolving asset->url in your GROQ query, you can use the @sanity/asset-utils library to generate file URLs client-side. This approach is more efficient since you only need the asset reference ID:
"body": body[]{
...,
_type == 'file' => {
...,
// Just include the asset reference, resolve URL client-side
}
}Then in your code:
import { buildFileUrl } from '@sanity/asset-utils'
const fileUrl = buildFileUrl(fileBlock.asset, {
projectId: 'your-project-id',
dataset: 'your-dataset'
})Both approaches work, but using @sanity/asset-utils can be more performant since it avoids the extra dereferencing in GROQ. However, if you also need metadata like file size and original filename, you'll still need to resolve those from the asset in your query.
Show original thread12 replies
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.