How to show thumbnail/title from deeply nested objects in a schema

3 replies
Last updated: Feb 27, 2023
I have a schema that looks like below.
The issue is that I want to show the thumbnail/title from deeply nested objects and I'm not sure how to achieve that.
content
can be either
photo
or
video
and they have slightly different structure. But it seems like I need to use dot notation in the
select
to be able to get the reference properly, does that mean I should do something like:

select: {
  videoContent: 'content.[0].video',
  imageContent: 'content.[0].image.asset.originalFilename'
  layout: 'layout',
  bleed: 'bleed',
},
and then use
prepare
to determine if it's if type photo or video? Feels like there must be a better way.

production.js:
export default defineType({
  name: 'production',
  title: 'Productions',
  type: 'document',
  fields: [
    defineField({
      title: 'Content',
      name: 'content',
      type: 'array',
      of: [{
        type: 'productionContent',
      }]
    }),
  ],
})

productionContent.js:
export default {
  name: 'productionContent',
  title: 'Content',
  type: 'object',
  fields: [{
    name: 'content',
    title: 'Content',
    type: 'content',
  }],
  preview: {
    select: {
      content: 'content.[0]', <-- how to get either video or photo content here?
    },
    prepare(selection) {
      return {
        ...selection,
      }
    }
  }
}

content.js:
export default {
  name: 'content',
  title: 'Content',
  type: 'array',
  of: [
    {
      title: 'Photo',
      name: 'photo',
      type: 'photo',
    },
    {
      title: 'Video',
      name: 'video',
      type: 'video',
    },
  ]
}

photo.js
export default {
  name: 'photo',
  title: 'Photo',
  type: 'object',
  fields: [
    {
      title: 'Photo',
      name: 'image',
      type: 'image',
    },
  ],
  preview: {
    select: {
      media: 'image',
      title: 'image.asset.originalFilename'
    },
    prepare(selection) {
      return {
        ...selection,
        status: 'Photo'
      }
    }
  },
}

video.js
export default {
  name: 'video',
  title: 'Video',
  type: 'object',
  fields: [
    {
      title: 'Title',
      name: 'title',
      type: 'string',
    },
    {
      title: 'Video',
      name: 'video',
      type: 'mux.video',
    },
    {
      title: 'Preview Image',
      name: 'image',
      type: 'image',
    }
  ],
  preview: {
    select: {
      title: 'title',
      media: 'image'
    },
    prepare(selection) {
      return {
        ...selection,
        status: 'Video', 
      }
    }
  },
}
AI Update

Looking at your schema, you're dealing with nested arrays and union types, which makes preview selection tricky. The good news is that Sanity's select in preview configuration supports accessing nested properties, and you can handle the union type logic in the prepare function.

Your intuition is correct - you'll need to select the fields you need using dot notation and then determine the type in prepare. Here's the recommended approach:

// productionContent.js
export default {
  name: 'productionContent',
  title: 'Content',
  type: 'object',
  fields: [{
    name: 'content',
    title: 'Content',
    type: 'content',
  }],
  preview: {
    select: {
      // Select the first item's type to determine photo vs video
      contentType: 'content.0._type',
      // Select photo fields
      photoImage: 'content.0.image',
      photoFilename: 'content.0.image.asset.originalFilename',
      // Select video fields
      videoTitle: 'content.0.title',
      videoImage: 'content.0.image',
    },
    prepare(selection) {
      const { contentType, photoImage, photoFilename, videoTitle, videoImage } = selection;
      
      if (contentType === 'photo') {
        return {
          title: photoFilename || 'Untitled Photo',
          subtitle: 'Photo',
          media: photoImage,
        }
      }
      
      if (contentType === 'video') {
        return {
          title: videoTitle || 'Untitled Video',
          subtitle: 'Video',
          media: videoImage,
        }
      }
      
      return {
        title: 'No content',
      }
    }
  }
}

The key points:

  1. Use content.0._type to determine which union type you're dealing with. Every Sanity object has a _type field that identifies its schema type.

  2. Select all possible fields you might need from both photo and video types. The ones that don't exist for the actual type will just be undefined.

  3. Branch in prepare based on the _type to return the appropriate title, subtitle, and media.

This is indeed the standard pattern for handling union types in preview configurations. While it feels verbose, it's the most reliable way to handle polymorphic content in Sanity previews.

Alternative consideration: If your content array typically only has one item (which seems to be the case from your schema structure), you might want to reconsider if you need the array wrapper at all. If productionContent.content is always a single item, you could make it a direct union type instead of an array, which would simplify your preview configuration to just content._type, content.image, etc. without the array indexing.

export default {
  name: 'productionContent',
  title: 'Content',
  type: 'object',
  fields: [{
    name: 'content',
    title: 'Content',
    type: 'content',
  }],
  preview: {
    select: {
      content: 'content.[0]', <-- how to get either video or photo content here?
    },
    prepare(selection) {
      return {
        ...selection,
      }
    }
  }
}
What I normally do to double check I have correct values is console.log
selection
itself.
So like:


...
prepare(selection) {
   console.log('selection', selection);
   ...
}
I would open browser console and see the if the keys I have gathered from from
select
are actually there and correct.
yeah, the issue seems to be that references are not actually populated unless you specifically ask for a nested value
yeah. I just really rely on that trick to double check what I’m getting.

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.

Was this answer helpful?