Everything *[NYC] 2026: see what we announced.

When a Schema Starts Growing in Too Many Directions

When a Sanity object needs multiple variants, it opens up two flexible design paths: conditional fields that offer a smooth editor experience, and single‑item arrays that maintain a clean, type‑safe schema. While Sanity doesn’t yet provide a native pattern that combines both strengths, this creates room for thoughtful custom approaches that balance schema clarity with great editorial UX.

By Padmaja Seshadri


Approach A — Single Object With Conditional Fields


{
  name: "contentType",
  type: "string",
  options: { list: ["video", "image", "article"] }
},

{
  name: "videoUrl",
  type: "url",
  hidden: ({ parent }) => parent.contentType !== "video"
},

{
  name: "image",
  type: "image",
  hidden: ({ parent }) => parent.contentType !== "image"
}

Approach B: Base Object + specialized types in an array


{
  name: "content",
  type: "array",
  of: [
    { type: "videoContent" },
    { type: "imageContent" },
    { type: "articleContent" }
  ],
  validation: Rule => Rule.length(1)
}

ObjectPickerArrayInput

import { AddCircleIcon } from '@sanity/icons'
import { Button, Flex, Text, Tooltip } from '@sanity/ui'
import { customAlphabet } from 'nanoid'
import { useCallback } from 'react'
import { type ArrayOfObjectsInputProps, set } from 'sanity'

const nanoid = customAlphabet('1234567890abcdef', 12)

type ObjectPickerArrayInputProps = {
  value?: [{ _key: string; _type: string }]
} & ArrayOfObjectsInputProps

export const ObjectPickerArrayInput = (props: ObjectPickerArrayInputProps) => {
  const { renderDefault, value, schemaType, onChange } = props
  const handleClick = useCallback(
    (it: any) => {
      onChange(set([{ _type: it.name, _key: nanoid() }], []))
    },
    [onChange],
  )

  return (
    <>
        <Flex align='center' wrap='wrap' gap={3}>
          {schemaType.of.map(it => {
            const { icon: Icon } = it
             return  <Tooltip
                key={it.name}
                content={
                  <Text muted size={1}>
                    {it.title}
                  </Text>
                }
                animate
                fallbackPlacements={['right', 'left']}
                placement='bottom'
                portal
              >
                <Button
                  selected={value && value[0]._type === it.name}
                  fontSize={[2, 2, 3]}
                  iconRight={Icon}
                  padding={[3, 3, 4]}
                  mode='ghost'
                  tone='default'
                  onClick={()=>handleClick(it)}
                />
              </Tooltip>
          })}
        </Flex>
      {renderDefault(props)}
    </>
  )
}

Final flexible object type

import { ObjectPickerArrayInput } from '../components/ObjectPickerArrayInput'

{
  name: 'content',
  title: 'Content',
  type: 'array',
  of:[internalReference,externalLink],
  components: {
        input: ObjectPickerArrayInput,
      },
       options: {
          sortable: false,
          disableActions: ['add', 'addAfter', 'addBefore', 'duplicate', 'duplicate', 'copy','remove']
   },
}

In many real-world CMS implementations, you start with a simple document type — say a “Section” or “Block”. Over time, business requirements evolve, and that once-simple object now needs multiple variations, conditional behaviors, and occasional one-off fields.

This leads teams into two common approaches, both of which eventually become painful.

1. Approach A — Single Object With Conditional Fields

(“Just hide or show fields based on another field's value”) refer to code snippet.

2. Approach B — Base Object + Specialized Types in an Array

(“Keep a base type, and include a 1-item array of a specific subtype") refer code snippet

How We Try to Improve Approach B Using a Custom Array Input

Approach B solves the scalability issue (each variant gets its own type), but the editorial experience suffers because Sanity forces editors to interact with a 1‑item array.

To fix that, we customize the array input and make it behave more like a simple object selector — without exposing the “array-ness” of it.

Fixing the UX by Customizing the Array Input

Instead of showing the default “Add → Select Type → Expand Panel” interaction, we override the input component and present a very simple workflow:

  • schemaType.of gives us all allowed object types
  • we render one button per object
  • clicking a button inserts the correct _type into the array
  • Sanity takes over from there

Caveat:
Since, we are manually patching the objects, the initialValues are not set. This can be addressed by validating the presence of a value corresponding to initial value fields.

Contributor

Padmaja Seshadri

Developer

Visit Padmaja Seshadri's profile