Hide array items conditionally based on document field in Sanity

16 replies
Last updated: Jan 4, 2023
Is there still no way to hide items from an array? mostly want to offer a cleaner editing experience to my clients so something like this:
   {
      title: 'Modules',
      name: 'modules',
      group: 'content',
      type: 'array',
      of: [
        {
          type: 'page.jobListings',
          hidden: ({document}) => document?.pageTemplate !== 'jobs',
        },
AI Update

Based on the documentation, you still can't conditionally hide individual array member types from the insert menu using the hidden property on the array of members. This has been a long-standing limitation in Sanity Studio.

The hidden property works great for hiding entire fields or fields within objects, but it doesn't currently work for conditionally showing/hiding which types appear in an array's insert menu. So your example code with hidden on the array member won't work as intended:

// This won't work to hide the type from the insert menu
of: [
  {
    type: 'page.jobListings',
    hidden: ({document}) => document?.pageTemplate !== 'jobs', // ❌ Not supported
  },
]

Workarounds

While there's no perfect solution, here are some approaches to improve the editing experience:

1. Validation Instead of Hiding

You can prevent invalid array items from being saved using custom validation:

{
  name: 'modules',
  type: 'array',
  of: [{type: 'page.jobListings'}],
  validation: Rule => Rule.custom((modules, context) => {
    const doc = context.document;
    if (doc?.pageTemplate !== 'jobs' && modules?.some(m => m._type === 'page.jobListings')) {
      return 'Job listings module is only available for job template pages';
    }
    return true;
  })
}

2. Different Schemas for Different Templates

Create separate document types for different page templates, each with only the relevant array members. This is cleaner but requires more schema definitions.

3. Custom Array Input Component

Build a custom input component that filters the available types based on your conditions. This gives you full control but requires more development work.

4. Conditional Entire Arrays

Instead of conditionally hiding array members, you could have different array fields that are conditionally shown:

{
  name: 'jobModules',
  type: 'array',
  of: [{type: 'page.jobListings'}],
  hidden: ({document}) => document?.pageTemplate !== 'jobs'
},
{
  name: 'standardModules',
  type: 'array',
  of: [{type: 'hero'}, {type: 'content'}],
  hidden: ({document}) => document?.pageTemplate === 'jobs'
}

This approach is mentioned in this community answer as a potential workaround.

Unfortunately, there hasn't been an update to support this feature natively yet, so you'll need to choose one of these workarounds based on your specific needs and how much development effort you want to invest. The validation approach is probably the quickest win to prevent your clients from adding inappropriate modules, even if they still see them in the insert menu.

Show original thread
16 replies
I may just make 4-5 different module arrays that are hidden at the main level but would love to keep it cleaner in a single modules[] field
You can't use the hidden callback, but you can use a custom input that leverages the
renderDefault
method to dynamically hide items in just a few lines of code. Here's an example of one that I made that hides items based on the current user's role.
rad let me check this out (still a v3 noob stuck in my old ways)
Ah, yeah, I should mention that's V3 specific and won't work in V2. You can do something similar in V2, but it would be significantly more code!
i’m using v3!
Perf!
so i am logging the props and don’t seem to have access to the doc i am on? trying to search the docs do i need to pass the pageTemplate field into options somehow?
You now have to use the hook
useFormValue
to get a field from the current document. For example:
import {useFormValue} from 'sanity'

const title = useFormValue(['title'])
💅 niiice
i think the only thing i noticed is the useFormValue doesn’t trigger right away does the field input need to be async?
like if i update the formValue(‘pageTemplate’) i actually need to hit it twice before it invalidates the input component
so basically if i switch to jobs (pageTemplate) and then to something else it shows me the jobs options after i switch and not when I initiall switched

const pageBlocks = [
  {type: 'page.jobListings', options: { template: 'jobs' }},
  {type: 'page.marquee', options: { template: 'any' }},
]

function PageTemplateInput(props) {
  const {schemaType, renderDefault} = props

  // Get active template
  const pageTemplate = useFormValue(['pageTemplate'])
  // filter allowed modules by the template type
  const allowedTypes = 
    schemaType.of.filter(({ options }) => {
      return options.template === pageTemplate || options.template === 'any'
    })
      
  //render the default component and replace the allowed types
  return renderDefault({...props, schemaType: {...schemaType, of: allowedTypes}})
}
Ah, that's currently a bug that's being addressed 😬
i’m always on the bleeding edge of bug finding
i can work around it for now cause i do think this is a much nicer experience

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?