How to Customize Document Previews in Sanity CMS Studio

Use select and prepare to show dates, status, references, and computed info

·Matija Žiberna·
How to Customize Document Previews in Sanity CMS Studio

📋 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.

No spam. Unsubscribe anytime.

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

0

Comments

Leave a Comment

Your email will not be published

10-2000 characters

• Comments are automatically approved and will appear immediately

• Your name and email will be saved for future comments

• Be respectful and constructive in your feedback

• No spam, self-promotion, or off-topic content

Matija Žiberna
Matija Žiberna
Full-stack developer, co-founder

I'm Matija Žiberna, a self-taught full-stack developer and co-founder passionate about building products, writing clean code, and figuring out how to turn ideas into businesses. I write about web development with Next.js, lessons from entrepreneurship, and the journey of learning by doing. My goal is to provide value through code—whether it's through tools, content, or real-world software.

You might be interested in

How to Customize Document Previews in Sanity CMS Studio | Build with Matija