Schema design: Let editors choose from multiple source fields or custom value
Your use case is definitely achievable and yes, you'll want a custom input component! This is a great pattern for handling data from external systems while giving editors control over what gets published.
Schema Structure
First, set up your schema to store all the data:
{
name: 'project',
type: 'document',
fields: [
{
name: 'sourceData',
type: 'object',
readOnly: true, // Lock these fields from manual editing
fields: [
{name: 'shortName', type: 'string'},
{name: 'longName', type: 'string'},
{name: 'alias', type: 'string'}
]
},
{
name: 'displayName',
type: 'object',
components: {
input: ProjectNameSelector // Your custom component
},
fields: [
{
name: 'selected',
type: 'string',
options: {
list: ['shortName', 'longName', 'alias', 'custom']
}
},
{
name: 'customValue',
type: 'string'
}
]
}
]
}Custom Input Component
Based on the custom input components documentation, here's a simplified approach:
import {set, unset} from 'sanity'
import {Stack, Radio, TextInput, Card, Text} from '@sanity/ui'
export function ProjectNameSelector(props) {
const {value, onChange, elementProps} = props
const sourceData = props.document?.sourceData || {}
const handleSelectionChange = (selection) => {
onChange(set({
selected: selection,
customValue: value?.customValue
}))
}
const handleCustomChange = (event) => {
onChange(set({
selected: 'custom',
customValue: event.target.value
}))
}
return (
<Stack space={3}>
<Radio
checked={value?.selected === 'shortName'}
onChange={() => handleSelectionChange('shortName')}
>
<Text>Short Name: {sourceData.shortName}</Text>
</Radio>
<Radio
checked={value?.selected === 'longName'}
onChange={() => handleSelectionChange('longName')}
>
<Text>Long Name: {sourceData.longName}</Text>
</Radio>
<Radio
checked={value?.selected === 'alias'}
onChange={() => handleSelectionChange('alias')}
>
<Text>Alias: {sourceData.alias}</Text>
</Radio>
<Card padding={3} border>
<Stack space={2}>
<Radio
checked={value?.selected === 'custom'}
onChange={() => handleSelectionChange('custom')}
>
<Text>Custom</Text>
</Radio>
{value?.selected === 'custom' && (
<TextInput
value={value?.customValue || ''}
onChange={handleCustomChange}
/>
)}
</Stack>
</Card>
</Stack>
)
}Querying the Final Value
When querying for your frontend, you can use GROQ to resolve the selected value:
*[_type == "project"] {
displayName: select(
displayName.selected == "shortName" => sourceData.shortName,
displayName.selected == "longName" => sourceData.longName,
displayName.selected == "alias" => sourceData.alias,
displayName.selected == "custom" => displayName.customValue
)
}Tips
- Keep source data separate: The
readOnly: trueonsourceDataprevents accidental edits - Use Sanity UI components: The
@sanity/uilibrary (Radio, Card, TextInput) keeps your component looking native - Access document context: Use
props.documentto read other fields (like your source data) - Handle patches properly: Use the
set()helper from thesanitypackage as shown in the custom input components guide
This pattern works really well for systems-of-record scenarios where you want source data preserved but give editors control over presentation. You could even enhance it further by showing a preview of what the selected name looks like in your frontend design!
Show original thread8 replies
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.