How to Customize Document Previews in Sanity CMS Studio
Use select and prepare to show dates, status, references, and computed info

📋 Complete Sanity Development Guides
Get practical Sanity guides with working examples, schema templates, and time-saving prompts. Everything you need to build faster with Sanity CMS.
Related Posts:
Last week, I was working on a client project where content creators needed to see publication dates and other key information directly in their Sanity Studio document lists. The default preview only shows title and maybe an image, but when you're managing scheduled content or complex document types, you need more context at a glance. After implementing custom previews across several document types, I'm sharing the complete process for tailoring how your documents appear in Sanity Studio lists.
Understanding Sanity's Preview System
Sanity's preview system controls how documents appear in Studio lists, search results, and reference inputs. Every document type can have a custom preview configuration that determines which fields are displayed and how they're formatted. The preview system consists of two main parts: field selection and data preparation.
By default, Sanity shows just the document title and perhaps a main image. This works for simple content, but complex workflows require more information. The preview configuration lives in your document schema and gives you complete control over what content creators see when browsing documents.
Basic Preview Configuration
Here's how a typical document preview is structured:
// File: src/lib/sanity/schemaTypes/postType.ts
export const postType = defineType({
name: 'post',
title: 'Post',
type: 'document',
fields: [
// ... your field definitions
],
preview: {
select: {
title: 'title',
subtitle: 'subtitle',
media: 'mainImage',
},
prepare(selection) {
return {
title: selection.title,
subtitle: selection.subtitle,
media: selection.media,
}
},
},
})
The select
object defines which fields from your document to include in the preview. The prepare
function receives those selected fields and returns the final preview object that Sanity displays. This two-step process gives you flexibility to transform and format data before it appears in the interface.
Adding Custom Fields to Preview
Let's extend this basic setup to show publication dates and other relevant information. First, expand the field selection to include the data you need:
// File: src/lib/sanity/schemaTypes/postType.ts
preview: {
select: {
title: 'title',
subtitle: 'subtitle',
author: 'author.name',
media: 'mainImage',
publishedAt: 'publishedAt',
isHowTo: 'isHowTo',
gallery: 'gallery',
},
prepare(selection) {
const { author, subtitle, publishedAt, isHowTo, gallery } = selection
// We'll customize this in the next step
return {
title: selection.title,
subtitle: subtitle,
media: selection.media,
}
},
}
Notice how you can select fields from referenced documents using dot notation (author.name
) and access array fields like gallery
. Sanity automatically resolves these relationships when building the preview.
Formatting and Combining Data
The real power comes in the prepare
function where you can format dates, combine multiple fields, and create conditional displays:
// File: src/lib/sanity/schemaTypes/postType.ts
prepare(selection) {
const { author, subtitle, publishedAt, isHowTo, gallery } = selection
let subtitleText = ''
// Format the published date
const publishedDate = publishedAt ? new Date(publishedAt).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
}) : ''
if (publishedDate) {
subtitleText = publishedDate
}
// Add conditional content type indicator
if (isHowTo) {
subtitleText = subtitleText ? `${subtitleText} • 📋 How-To Guide` : '📋 How-To Guide'
} else if (subtitle) {
subtitleText = subtitleText ? `${subtitleText} • ${subtitle}` : subtitle
} else if (author) {
const authorText = `by ${author}`
subtitleText = subtitleText ? `${subtitleText} • ${authorText}` : authorText
}
// Add gallery count if images exist
if (gallery && gallery.length > 0) {
const galleryText = `${gallery.length} gallery image${gallery.length === 1 ? '' : 's'}`
subtitleText = subtitleText ? `${subtitleText} • ${galleryText}` : galleryText
}
return {
title: selection.title,
subtitle: subtitleText,
media: selection.media,
}
},
This approach creates rich, informative previews. You might see something like "Jan 15, 2024 • 📋 How-To Guide • 3 gallery images" in your document list, giving content creators immediate context about each post.
Advanced Preview Patterns
Conditional Media Selection
Sometimes you want different images based on document content:
prepare(selection) {
const { title, mainImage, gallery, video } = selection
// Use video thumbnail if available, otherwise main image, otherwise first gallery image
let media = mainImage
if (!media && video) {
media = selection.videoThumbnail
}
if (!media && gallery && gallery.length > 0) {
media = gallery[0]
}
return {
title,
media,
subtitle: `${gallery?.length || 0} images • ${video ? 'Has video' : 'No video'}`
}
}
Status-Based Formatting
For documents with workflow states, you can indicate status visually:
prepare(selection) {
const { title, status, publishedAt } = selection
let subtitle = ''
let title_formatted = title
if (status === 'draft') {
title_formatted = `[DRAFT] ${title}`
subtitle = 'Not published'
} else if (status === 'scheduled') {
subtitle = `Scheduled: ${new Date(publishedAt).toLocaleDateString()}`
} else if (status === 'published') {
subtitle = `Published: ${new Date(publishedAt).toLocaleDateString()}`
}
return {
title: title_formatted,
subtitle,
media: selection.media,
}
}
Reference Field Details
When working with references, you can show related information:
select: {
title: 'title',
categoryTitle: 'category.title',
categoryColor: 'category.color',
authorName: 'author.name',
authorImage: 'author.image',
},
prepare(selection) {
const { categoryTitle, authorName } = selection
return {
title: selection.title,
subtitle: `${categoryTitle} • by ${authorName}`,
media: selection.authorImage,
}
}
Applying to Different Document Types
This pattern works for any document type. Here's how you might customize a product preview:
// File: src/lib/sanity/schemaTypes/productType.ts
preview: {
select: {
title: 'name',
price: 'price',
inventory: 'inventory',
category: 'category.title',
image: 'images.0', // First image from array
onSale: 'onSale',
},
prepare(selection) {
const { price, inventory, category, onSale } = selection
const priceText = `$${price}`
const stockText = inventory > 0 ? `${inventory} in stock` : 'Out of stock'
const saleText = onSale ? 'ON SALE' : ''
const subtitle = [saleText, priceText, stockText, category]
.filter(Boolean)
.join(' • ')
return {
title: selection.title,
subtitle,
media: selection.image,
}
},
}
This creates previews like "ON SALE • $29.99 • 15 in stock • Electronics" that give immediate business context.
Complex Data Transformations
Sometimes you need to perform calculations or complex logic:
prepare(selection) {
const { title, tasks, totalEstimate } = selection
const completedTasks = tasks?.filter(task => task.completed).length || 0
const totalTasks = tasks?.length || 0
const progressPercent = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0
const estimateHours = Math.round(totalEstimate / 60) // Convert minutes to hours
return {
title,
subtitle: `${progressPercent}% complete • ${completedTasks}/${totalTasks} tasks • ${estimateHours}h estimated`,
media: selection.media,
}
}
Best Practices for Preview Customization
Keep preview subtitles concise but informative. Users scan these quickly, so prioritize the most important information first. Use consistent separators like bullets (•) to create visual structure, and consider using emoji sparingly for quick visual categorization.
Test your previews with real data to ensure they handle edge cases gracefully. Empty fields, missing references, and null values should all be handled elegantly in your prepare function.
Remember that previews are rebuilt whenever documents change, so avoid expensive operations in the prepare function. Simple data formatting and concatenation work well, but avoid API calls or complex calculations.
Conclusion
Custom document previews transform the Sanity Studio experience from a basic list of titles into an information-rich interface that helps content creators work efficiently. By combining the select
and prepare
pattern, you can show dates, status indicators, related content, and calculated values directly in document lists.
The key insight is that the select
object defines your data requirements, while the prepare
function gives you complete control over formatting and presentation. This pattern works for any document type and scales from simple date formatting to complex business logic display.
Want to take your Sanity Studio customization further? Check out my guide on adding custom sorting options to document lists for complete control over how your content is organized.
Let me know in the comments if you have questions about customizing previews for your specific document types, and subscribe for more practical Sanity CMS guides.
Thanks, Matija