Sanity Pioneers: Get early access to betas, extra AI credits, and a direct line to the engineering team. Apply now

Conditional field values based on another document type in Sanity

8 repliesLast updated: Nov 29, 2025

Question about configuring the schemas in the Studio:
I have a document type called
position
to represent a political position of a public figure. The position document has the fields
issue
(the name of the issue, i.e. abortion, gun control, etc), and
stance
, i.e. pro-choice, pro-life, pro-gun control, pro-2nd amendment, etc.
What I would like to do is have the value of
issue
and the value of
stance
to be restricted based on another document type called
positionDefinition
. So,
positionDefinition
would have the fields
issueName
(string) and
stances
(an array of strings). To create a
position
the editor has to first create a
positionDefinition
. Then they can create a
position
document where the
issue
field is one from a
positionDefinition
and the
stance
is one of the stances that were defined for that issue in
positionDefinition
.
Does that make sense, is it possible to do?
AI Update

Yes, this makes perfect sense and is absolutely possible! What you're describing is a dependent field pattern where the available options in one field are determined by the value of another field, and both are constrained by a separate definition document. This is a great use case for custom input components.

Here's how to implement it:

Schema Setup

First, define your schemas:

// positionDefinition.ts
export default {
  name: 'positionDefinition',
  title: 'Position Definition',
  type: 'document',
  fields: [
    {
      name: 'issueName',
      title: 'Issue Name',
      type: 'string',
      validation: Rule => Rule.required()
    },
    {
      name: 'stances',
      title: 'Stances',
      type: 'array',
      of: [{type: 'string'}],
      validation: Rule => Rule.required().min(1)
    }
  ]
}

// position.ts
import { IssueInput } from './components/IssueInput'
import { StanceInput } from './components/StanceInput'

export default {
  name: 'position',
  title: 'Position',
  type: 'document',
  fields: [
    {
      name: 'issue',
      title: 'Issue',
      type: 'string',
      components: {
        input: IssueInput
      }
    },
    {
      name: 'stance',
      title: 'Stance',
      type: 'string',
      components: {
        input: StanceInput
      }
    }
  ]
}

Custom Input Components

Now create the custom components using useFormValue and useClient:

// components/IssueInput.tsx
import { StringInputProps, useClient } from 'sanity'
import { useEffect, useState } from 'react'
import { Select } from '@sanity/ui'

export function IssueInput(props: StringInputProps) {
  const { value, onChange } = props
  const client = useClient({ apiVersion: '2024-01-01' })
  const [issues, setIssues] = useState<Array<{ _id: string; issueName: string }>>([])

  useEffect(() => {
    client
      .fetch('*[_type == "positionDefinition"]{ _id, issueName }')
      .then(setIssues)
  }, [client])

  return (
    <Select
      value={value || ''}
      onChange={(event) => onChange(event.currentTarget.value)}
    >
      <option value="">Select an issue...</option>
      {issues.map((issue) => (
        <option key={issue._id} value={issue.issueName}>
          {issue.issueName}
        </option>
      ))}
    </Select>
  )
}

// components/StanceInput.tsx
import { StringInputProps, useClient, useFormValue } from 'sanity'
import { useEffect, useState } from 'react'
import { Select, Stack, Text } from '@sanity/ui'

export function StanceInput(props: StringInputProps) {
  const { value, onChange } = props
  const client = useClient({ apiVersion: '2024-01-01' })
  
  // Access the current document's issue field using useFormValue
  const selectedIssue = useFormValue(['issue']) as string | undefined
  
  const [stances, setStances] = useState<string[]>([])

  useEffect(() => {
    if (!selectedIssue) {
      setStances([])
      return
    }

    client
      .fetch(
        '*[_type == "positionDefinition" && issueName == $issue][0].stances',
        { issue: selectedIssue }
      )
      .then((result) => setStances(result || []))
  }, [client, selectedIssue])

  if (!selectedIssue) {
    return (
      <Stack space={3}>
        <Text size={1} muted>
          Please select an issue first
        </Text>
      </Stack>
    )
  }

  return (
    <Select
      value={value || ''}
      onChange={(event) => onChange(event.currentTarget.value)}
    >
      <option value="">Select a stance...</option>
      {stances.map((stance) => (
        <option key={stance} value={stance}>
          {stance}
        </option>
      ))}
    </Select>
  )
}

How It Works

The key here is the useFormValue hook, which replaced the older withDocument HOC in Studio v3. It allows you to access any field value in the current document, enabling the dependent field behavior you need.

Alternative: Using References for Better Data Integrity

Instead of storing the issue as a string, you could use a reference field for even better data integrity:

{
  name: 'issueRef',
  title: 'Issue',
  type: 'reference',
  to: [{type: 'positionDefinition'}]
}

Then in your StanceInput, you'd dereference it:

const issueRef = useFormValue(['issueRef']) as { _ref: string } | undefined

useEffect(() => {
  if (!issueRef?._ref) {
    setStances([])
    return
  }
  
  client
    .fetch('*[_id == $id][0].stances', { id: issueRef._ref })
    .then((result) => setStances(result || []))
}, [client, issueRef])

This approach ensures that if you rename an issue in positionDefinition, all existing position documents automatically reflect the change, and you maintain referential integrity.

Important Notes

Show original thread
8 replies

Was this answer helpful?

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.

Related contributions