Filter duplicate references & set default author in reference array

22 replies
Last updated: Sep 27, 2021
2 questions regarding an array of references:
1. Is there an easy way to filter out any already references already added to the parent array from the list of available array references in the default dialog dropdown? Should this potentially be default behavior (or a setting)?
2. I'm trying to reference a default author for my post document type, but since its an array of a reference, I'm not quite sure how to nest it.
This is my current attempt that is not working:

// post.js
export default {
...
  {
      name: 'authors',
      title: 'Authors',
      type: 'array',
      initialValue: {
        _type: 'reference',
        to: {
          _type: 'person',
          _ref: params.'9122a1cc-40d0-4120-9f30-3e997007896c'
        }
      },
  },
...
}
AI Update

I can help you with both questions about arrays of references in Sanity!

1. Filtering out already-selected references from the dropdown

Yes, there's a way to filter out already-selected references from showing in the dropdown. You can use the options.filter property on your reference field inside the array. Here's how:

{
  name: 'projects',
  title: 'Projects',
  type: 'array',
  of: [
    {
      type: 'reference',
      to: [{type: 'sampleProject'}],
      options: {
        filter: ({document}) => {
          const existingRefs = document.projects?.map(item => item._ref).filter(Boolean) || []
          return {
            filter: '!(_id in $existingRefs)',
            params: {
              existingRefs
            }
          }
        }
      }
    }
  ]
}

This dynamically filters the reference list based on what's already been added to the array. The key is that the options go on the reference type inside the array, not on the array itself. More details on dynamic filters can be found in the reference type documentation.

You can also use the validation rule unique() to prevent duplicates, but that only throws an error after selection rather than hiding already-selected items from the list.

To make this reusable across multiple schemas, you can extract it into a utility function:

// utils/getFilter.js
export const getFilter = (document, field) => {
  const existingEntries = document[field]?.map(entry => entry._ref).filter(Boolean) || []
  return {
    filter: '!(_id in $existingEntries)',
    params: {
      existingEntries
    }
  }
}

// Then in your schema:
import { getFilter } from '../utils/getFilter'

options: {
  filter: ({document}) => getFilter(document, 'projects')
}

As for whether this should be default behavior - it's not currently, but the filtering approach gives you that control when you need it.

2. Setting a default reference in an array

For setting an initial value with a reference in an array, the initialValue needs to be an array containing reference objects. Here's the correct structure:

{
  name: 'authors',
  title: 'Authors',
  type: 'array',
  of: [
    {
      type: 'reference',
      to: [{type: 'person'}]
    }
  ],
  initialValue: [
    {
      _type: 'reference',
      _ref: '9122a1cc-40d0-4120-9f30-3e997007896c'
    }
  ]
}

The key differences from your attempt:

  • initialValue should be an array (since the field type is array)
  • Inside that array, you only need _type: 'reference' and _ref - you don't need the nested to property in the initial value
  • The _type should be 'reference', not 'person'

This will automatically populate the array with your default author when a new document is created, and editors can still add more authors or remove the default one.

Note that this only works for new documents. If you need to add this default to existing documents, you'd need to use the Mutations API to update them. You can read more about initial values in the Initial Value Templates documentation.

Show original thread
22 replies
For question 1, you'll want to use the
unique()
validation rule. There's also the
filter
option, which in your case would look something like this:
options: {
  filter: ({ document }) => {
    const existingAuthors = document.authors.map(author => author._ref).filter(Boolean)
    return {
      filter: '!(_id in $existingAuthors)',
      params: {
        existingAuthors
      }
    }
  }
}
There's more on setting up dynamic filters
here .
With your initial value, is it a
parameterized template or just a static predefined author reference you're looking to use?
Oh, and that filter option goes on your authors array, sorry didn't make that clear in the original method.
No worries, I thought that made sense.
Regarding the
Filtering selected elements out of the reference array:I've added the validation rule and the options with some changes based on the array object, although the filter doesn't seem to be working correctly (the validation rule is though).

Expected state: What I'd expect to see in this case is that once I've added one project to my array of references that its no longer available in the selection options.
Observable state: projects that have been previously added are still visible among list of options, and return an error once added again

Here's this document's code:
//recommendation.js

export default {
  title: 'Recommendation',
  name: 'recommendation',
  type: 'document',
  fields: [
  ...
   {
      title: 'Projects',
      name: 'projects',
      type: 'array',
      of: [
        {
          title: 'Related Projects',
          type: 'reference',
          to: {type: 'sampleProject'}
        }
      ],
      validation: Rule => Rule.unique(),
      options: {
        filter: ({document}) => {
          const existingProjects = document.projects.map(project => project._ref).filter(Boolean)
          return {
            fitlers: '!(_id in $existingprojects)',
            params: {
              existingProjects
            }
          }
        }
      }
    }
...
}
Do I need to change the document filter to specifically reference the projects array?
Regarding the set an initial value of a referenced author array, I don't think its a parameterized template. I just want to reference a single person among my person's document as the default author, with no changes to the persons document itself, and no new persons added as initial values as well
Ah, yes the validation option won't remove the option from the list, just throw an error when you do select it. That's why sometimes I prefer to just filter the list so that it's not even an option. And I was wrong when I said the filter goes on your authors array 😅 . Blame it on it being Monday morning. It goes on your reference inside the array!Also, you had
fitlers
and
$exsitingprojects
, so once I changed them to
filter
and
$existingProjects
, plus corrected my misleading placement I got it to work!
export default {
  title: 'Recommendation',
  name: 'recommendation',
  type: 'document',
  fields: [
   {
      title: 'Projects',
      name: 'projects',
      type: 'array',
      of: [
        {
          title: 'Related Projects',
          type: 'reference',
          to: [{type: 'ref'}],
          options: {
            filter: ({document}) => {
              const existingProjects = document.projects.map(project => project._ref).filter(Boolean)
              return {
                filter: '!(_id in $existingProjects)',
                params: {
                  existingProjects
                }
              }
            }
          }
        }
      ],
      
    }
  ]
}
Ah, yes the validation option won't remove the option from the list, just throw an error when you do select it. That's why sometimes I prefer to just filter the list so that it's not even an option. And I was wrong when I said the filter goes on your authors array 😅 . Blame it on it being Monday morning. It goes on your reference inside the array!Also, you had
fitlers
and
$exsitingprojects
, so once I changed them to
filter
and
$existingProjects
, plus corrected my misleading placement I got it to work!
export default {
  title: 'Recommendation',
  name: 'recommendation',
  type: 'document',
  fields: [
   {
      title: 'Projects',
      name: 'projects',
      type: 'array',
      of: [
        {
          title: 'Related Projects',
          type: 'reference',
          to: [{type: 'ref'}],
          options: {
            filter: ({document}) => {
              const existingProjects = document.projects.map(project => project._ref).filter(Boolean)
              return {
                filter: '!(_id in $existingProjects)',
                params: {
                  existingProjects
                }
              }
            }
          }
        }
      ],
      
    }
  ]
}
With the initial value: if it's not parameterized, you'll need to tell it the
_type
and
_ref
:
{
      name: 'authors',
      title: 'Authors',
      type: 'array',
      of: [
        { type: 'author' }
      ],
      initialValue: [
        {
          _type: 'author',
          _ref: '<id-of-author-doc>'
        }
      ]
    },
This will only work on new documents, though. If you'd like to add it to previously created docs, you would have to mutate them via the Mutations API.
With the initial value: if it's not parameterized, you'll need to tell it the
_type
and
_ref
:
{
      name: 'authors',
      title: 'Authors',
      type: 'array',
      of: [
        { type: 'author' }
      ],
      initialValue: [
        {
          _type: 'author',
          _ref: '<id-of-author-doc>'
        }
      ]
    },
This will only work on new documents, though. If you'd like to add it to previously created docs, you would have to mutate them via the Mutations API.
user M
the array reference filter works as expected! 🙌 Do you have any recommendation for how I could move it into a separate
utils
area of sanity and re-use that across other of my references to keep my other references DRY with the same functionality? Or is this more appropriate to build a custom Sanity Reference component?
Also, big lol on
fitlers
🤦‍♂️ I totally didn't catch it until re-reading it 3 times so no worries.

Regarding the initial value reference in an array, I see your code example includes author as if its an object. Here's what my code looks like today
{
      name: 'authors',
      title: 'Authors',
      type: 'array',
      of: [
        {
          type: 'reference', to: {type: 'person'}
        }
      ],
      initialValue: {
        _type: 'reference',
        to: {

          _type: 'person',
          _ref: '9122a1cc-40d0-4120-9f30-3e997007896c'
        }
      },
    },
Should I instead add the initialValue to the reference in the array itself?

My ideal use case is that an editor already has a preassigned value in the array of that specific person reference, but can add additional and/or remove that one easily
I'll have to play with extracting the filter into a separate util, but that's a good idea. Especially as you start building custom Structures you'll find yourself repeating the same basic syntax in many places.
user M
so moving this initial value works in the sense that I still have to click the "add" button but then it autocompletes to my author everytime, which is not the behavior I'm looking for. Ideally, with the creation of each new post object, the author is already prefilled without clicking add, and then initialValue is left empty within the reference element so I don't get this behavior.
This is the code producing this behavior:

{
      name: 'authors',
      title: 'Authors',
      type: 'array',
      of: [
        {
          Title: 'Author',
          type: 'reference',
          to: {type: 'person'},
          initialValue: {
            _type: 'person',
            _ref: '9122a1cc-40d0-4120-9f30-3e997007896c'
          }
        }
      ]
    },
user M
so moving this initial value works in the sense that I still have to click the "add" button but then it autocompletes to my author everytime, which is not the behavior I'm looking for. Ideally, with the creation of each new post object, the author is already prefilled without clicking add, and then initialValue is left empty within the reference element so I don't get this behavior.
This is the code producing this behavior:

{
      name: 'authors',
      title: 'Authors',
      type: 'array',
      of: [
        {
          Title: 'Author',
          type: 'reference',
          to: {type: 'person'},
          initialValue: {
            _type: 'person',
            _ref: '9122a1cc-40d0-4120-9f30-3e997007896c'
          }
        }
      ]
    },
Ah, you're right, it would have to be something like this:
{
      name: 'authors',
      title: 'Authors',
      type: 'array',
      of: [
        { type: 'reference',
          to: [
            { type: 'person' }
          ]
        }
      ],
      initialValue: [
        {
          _ref: '<id-of-person-doc>'
        }
      ]
    },
Then, when you create a new document it should automatically add it without having to click anything.
Beautiful! Tested and working on Localhost! Big Thanks for all the help this Monday morning/afternoon (PST)
user M
!
Beautiful! Tested and working on Localhost! Big Thanks for all the help this Monday morning/afternoon (PST)
user M
!
Nothing quite like starting your week off with some fun challenges! Thanks for helping wake my brain up!
FYI, I DRY'ed that reference filter up a little with this. Just not sure how to replace the
document.[parentArray].map
in a meaningful way. I tried to create a const and pass the projects in the filter params but that resulted in
undefined.map
errors
options: {
            filter: ({document}) => {
              const existingReferences = document.projects.map(item => item._ref).filter(Boolean)
              return {
                filter: '!(_id in $existingReferences)',
                params: {
                  existingReferences
                }
              }
            }
          }
FYI, I DRY'ed that reference filter up a little with this. Just not sure how to replace the
document.[parentArray].map
in a meaningful way. I tried to create a const and pass the projects in the filter params but that resulted in
undefined.map
errors
options: {
            filter: ({document}) => {
              const existingReferences = document.projects.map(item => item._ref).filter(Boolean)
              return {
                filter: '!(_id in $existingReferences)',
                params: {
                  existingReferences
                }
              }
            }
          }
I got it working with:
// ./utils/getFilter.js

export const getFilter = (document , field) => {
  const existingEntries = document[field].map(existingEntry => existingEntry._ref).filter(Boolean)
  return {
    filter: '!(_id in $existingEntries)',
    params: {
      existingEntries
    }
  }
}
Then in your doc:

import { getFilter } from "../utils/getFilter"

export default {
  title: 'Recommendation',
  name: 'recommendation',
  type: 'document',
  fields: [
   {
      title: 'Projects',
      name: 'projects',
      type: 'array',
      of: [
        {
          title: 'Related Projects',
          type: 'reference',
          to: [{type: 'ref'}],
          options: {
            filter: ({document}) => {
              return getFilter(document, 'projects')
              }
            }
        }
      ],
      
    }
  ]
}
user M
You're a superstar!! 🌟 qq would I put the
/utils/getFilter.js
in my parent
studio
directory or within
schemas
?
nvm, reading your import and util header helped me figure out the correct answer is
/studio/utils...
Aw, thanks! Hmm, I usually put the
utils
or
lib
folders directly underneath the root Studio folder.
There's a really good guide on setting up your folder structure somewhere around here but I can't find it right now. I'll link it if I do!

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?