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.
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.ofgives us all allowed object types- we render one button per object
- clicking a button inserts the correct
_typeinto 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