Dynamic string options from Sanity document list in schema

2 replies
Last updated: Sep 8, 2023
I have a requirement where the string options list should come from sanity. Like list of all the pages created and show it in options list below.
{
      name: 'goToPage',
      title: 'Go to page',
      description: 'This will be the page you would like the users to go to.',
      type: 'string',
      options: { list: PAGE_TYPES }, //This is where I want the data to come from sanity rather than pre-defined.
    },
AI Update

You'll need to create a custom input component to achieve dynamic options from Sanity. Unfortunately, you can't use async functions or queries directly in the options.list field of a schema definition - it only accepts static arrays.

Here's how to solve this with a custom input component:

Create a Custom Input Component

First, create a custom input component that fetches pages dynamically using the useClient hook:

// components/PageSelectInput.tsx
import { useClient } from 'sanity'
import { StringInputProps, set, unset } from 'sanity'
import { useEffect, useState } from 'react'
import { Stack, Select } from '@sanity/ui'

export function PageSelectInput(props: StringInputProps) {
  const { value, onChange } = props
  const client = useClient({ apiVersion: '2024-01-01' })
  const [pages, setPages] = useState<Array<{ _id: string; title: string }>>([])
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    // Fetch all pages from Sanity
    client
      .fetch(`*[_type == "page"]{ _id, title, "slug": slug.current }`)
      .then((result) => {
        setPages(result)
        setLoading(false)
      })
      .catch((error) => {
        console.error('Error fetching pages:', error)
        setLoading(false)
      })
  }, [client])

  const handleChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
    const newValue = event.target.value
    onChange(newValue ? set(newValue) : unset())
  }

  return (
    <Stack space={3}>
      <Select
        value={value || ''}
        onChange={handleChange}
        disabled={loading}
      >
        <option value="">Select a page</option>
        {pages.map((page) => (
          <option key={page._id} value={page._id}>
            {page.title}
          </option>
        ))}
      </Select>
    </Stack>
  )
}

Use the Custom Component in Your Schema

Then reference this custom component in your schema:

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

{
  name: 'goToPage',
  title: 'Go to page',
  description: 'This will be the page you would like the users to go to.',
  type: 'string',
  components: {
    input: PageSelectInput
  }
}

Alternative: Use a Reference Field

If you're storing page IDs anyway, consider using Sanity's built-in reference field instead:

{
  name: 'goToPage',
  title: 'Go to page',
  type: 'reference',
  to: [{ type: 'page' }],
  options: {
    filter: '_type == "page"' // Optional: add custom filters
  }
}

This gives you a searchable dropdown with all pages automatically, without needing a custom component. The reference field also ensures data integrity and lets you easily resolve the full page document when querying.

The custom input approach gives you more control over the UI and what values are stored (like storing slugs instead of IDs), while the reference approach is simpler and more maintainable for typical use cases where you're linking to other documents.

Show original thread
2 replies
You would need to create a custom component to handle this. Here’s one that fetches an external API that you can adapt:
{
	title: 'Async List',
  name: 'asyncList',
  type: 'string',
  options: {
    list: [], // <-- list must be an empty array initially to avoid errors
    url: '', // <-- the url you want to fetch
		formatResponse: (data) => {} // <-- a function to transform the data to match the schema requirements: { title, value }
  }, 
	components: {
		input: AsyncListInput
	}
}

//input component
import {useState, useEffect} from 'react'

export const AsyncListInput = (props) => {
	const [listItems, setListItems] = useState([])
  const {schemaType, renderDefault} = props
  const {options} = schemaType
  const {url, formatResponse} = options

  useEffect(() => {
    const getListItems = () =>
      fetch(url)
        .then((res) => res.json())
        .then(formatResponse)
        .then(setListItems)

    getListItems()
  }, [url, formatResponse])

  return renderDefault({
    ...props,
    schemaType: {...schemaType, options: {...options, list: listItems}},
  })
}

//general usage
{
	name: 'catBreed',
	title: 'Cat Breed',
	type: 'asyncList',
	options: {
		url: '<https://catfact.ninja/breeds>', // <-- the url you want to fetch
    formatResponse: (json) => // <-- a function to transform the data to match the schema requirements
      json.data.map(({breed}) => ({
        title: breed,
        value: breed.toLowerCase().split(' ').join('-'),
      })),
		}
}
Thank you for your help.I will try this and get back if any help needed.

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?