Adding a custom preview component to a gallery object in Sanity

9 replies
Last updated: Jun 30, 2022
Hey, I try to add an array of images to my block-content array like this (not my exact schema but works for demonstration purposes):
{
 name: "blockContent",
 type: "array",
 of: [
  { type: "block" }, 
  { type: "object",
    fields: [{
      name: "gallery",
      type: "array",
      options: {layout: "grid"},
      of: [{type: "image"}]
    }]
  }
 ],
While the data from this schema looks exactly what you would expect, the presentation of the gallery object isn’t particularly pretty.

Any way to change that? Didn’t find a way to add a custom preview component to the editor that is not a block-type.
AI Update

Based on the recipe I found in the Sanity documentation, you can definitely add custom preview components to objects in Portable Text! Here's how to solve your problem:

You need to add a components key with a preview property to your object schema. Here's your example updated:

{
  name: "blockContent",
  type: "array",
  of: [
    { type: "block" }, 
    { 
      type: "object",
      name: "gallery",
      fields: [{
        name: "gallery",
        type: "array",
        options: {layout: "grid"},
        of: [{type: "image"}]
      }],
      preview: {
        select: { 
          images: 'gallery',
        },
      },
      components: {
        preview: ImageGalleryPreview,
      }, 
    }
  ],
}

Then create your custom preview component:

import { Flex, Stack } from '@sanity/ui'
import { urlFor } from './image-url-builder'

export default function ImageGalleryPreview(props) {
  const { images, schemaType } = props
  const schemaTitle = schemaType.title

  const modifiedProps = { 
    ...props, 
    title: schemaTitle,
  }

  return (
    <Stack space={[1]}>
      <>
        {props.renderDefault(modifiedProps)}
      </>
      <Flex 
        style={{ 
          gap: '5px',
          overflowX: 'scroll'
        }}
      >
        {images?.map((image) => (
          <img
            key={image._key}
            src={image.asset ? urlFor(image).url() : ''}
            style={{
              width: '400px',
              height: '200px',
              objectFit: 'cover',
            }}
            alt={image.alt}
          />
        ))}
      </Flex>
    </Stack>
  )
}

You'll also need an image URL builder:

import imageUrlBuilder from '@sanity/image-url'
import { client } from './sanity-client'

const builder = imageUrlBuilder(client)

export function urlFor(source) {
  return builder.image(source)
}

The key is using the preview configuration with select to choose which fields to pass to your component, and then the components.preview property to specify your custom React component. This gives you full control over how your gallery object appears in the Portable Text editor.

You can check out the full custom image gallery preview recipe in the Sanity documentation for more details!

hey, just solved this one myself in the last week ... let me grab my custom preview component real quick
ok, first here's my gallery component (mostly for reference)

import galleryPreview from '../../previews/GalleryPreview'

export default {
  title: "Image Gallery",
  name: "gallery",
  type: "object",
  preview: {
    select: {
      images: "images",
      columns: "columns"
    },
    component: galleryPreview
  },
  fields: [
    {
      title: "Columns",
      name: "columns",
      type: "number",
      initialValue: 3,
      validation: Rule => Rule.required().min(2).max(5)
    },
    {
      title: "Images",
      name: "images",
      type: "array",
      of: [{type: "imageFull"}],
      options: {
        layout: "grid"
      }
    }
  ]
}
and then my gallery preview component

import React from 'react'
import client from 'part:@sanity/base/client'
import urlBuilder from '@sanity/image-url'

const urlFor = source => urlBuilder(client).image(source)

const galleryPreview = ({ value = {} }) => {

  const cols = value.columns
  let wrapperStyles = {
    display: 'grid',
    gridTemplateColumns:`repeat(${cols}, 1fr)`,
    gap: `10px`,
    lineHeight: 0
  }
  let figureStyles = {
    margin: 0,
    aspectRatio: `1/1`,
  }
  let figureImgStyles = {
    width: `100%`,
    height: `100%`,
    objectFit: `cover`
  }

  return (
    <div style={wrapperStyles}>
      {value &&
        value.images.map(image => (
          <figure key={image._key} style={figureStyles}>
            <img src={urlFor(image).url()} style={figureImgStyles}/>
          </figure>
        ))
      }
    </div>
  )
}

export default galleryPreview
here's what it looks like in my studio
another example ... just to show its using my
columns
field to adjust the display grid
Awesome, that almost works.I can add images and afterwards activate the preview component.

Unfortunately the Desk crashes when I try to add new images with the component active. It tries to resolve the URL for an image with an ID that does not exist in my dataset. I’m a bit confused about this.
ahh, yes, i see that now... i hadn't used the component since i added the preview ... so it was working great for already existing galleries
i had the same error on another preview, and just looked at the gallery ... you'll want to add this

if (!value.images){
    return <span />
  }
I put this just before my return in the preview component
Great, thanks again. Tried to only check for existing values without returning anything instead. That obviously didnt work.

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?