Filter array to exclude already referenced sections in Sanity document

26 replies
Last updated: Jun 8, 2020
Hello amazing Sanity people 🌞 ! I am doing my head in trying to figure out how to write a filter query.I have a document with an array called
primary

primary
is an array of
item
objects.In item, instead of getting a list of all sections, I only want to see those which are not already referenced by my document.
Maybe this is actually very simple, but I can’t seem to work it out…

  name: 'item',
  title: 'Item',
  type: 'object',
  fields: [
    {
      name: 'section',
      title: 'Section',
      type: 'reference',
      to: { type: 'section' },
      options: {
        filter: ({ document, parent, parentPath }) => {
          console.log(document._id)

          return {
            filter: '!(^._id in *[_id==$document].primary[])',
            params: {
              document: document._id
            }
          }
        }
      }
    }
  ]

AI Update

I can help you filter out already-referenced sections from your reference field! The issue with your current query is the syntax - you're trying to access the parent document's array, but the approach needs adjustment.

Here's the corrected solution:

{
  name: 'section',
  title: 'Section',
  type: 'reference',
  to: { type: 'section' },
  options: {
    filter: ({ document }) => {
      // Get all section IDs already referenced in the primary array
      const referencedIds = document.primary
        ?.map(item => item.section?._ref)
        .filter(Boolean) || []

      return {
        filter: '!(_id in $referencedIds)',
        params: {
          referencedIds
        }
      }
    }
  }
}

What's happening here:

  1. Extract referenced IDs in JavaScript: Instead of trying to do this purely in GROQ, we extract the _ref values from the primary array items in the filter function itself. This is more reliable since you have direct access to the document object.

  2. Simple GROQ filter: The GROQ filter !(_id in $referencedIds) checks if the current section's _id is NOT in the array of already-referenced IDs.

  3. Pass as params: We pass the array of IDs as a parameter to the GROQ query.

Important note: The filter function re-runs when the document changes, so as you add sections to your primary array, they'll automatically disappear from the available options in other items.

Making it reusable:

You can extract this into a utility function to keep your code DRY, as shown in this community example:

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

// Then in your schema:
options: {
  filter: ({ document }) => {
    const referencedIds = document.primary
      ?.map(item => item.section?._ref)
      .filter(Boolean) || []
    
    return {
      filter: '!(_id in $referencedIds)',
      params: { referencedIds }
    }
  }
}

This is a common pattern when building navigation menus, related content pickers, or any scenario where you want to avoid duplicate references in an array!

Show original thread
26 replies
Could it be as simple as
!references($document)
?
I wish! That would only check if
section
has a direct reference to
document
but not if
document
has an array which has a reference to
section
Running
*[_id==$document][0].primary[].section[]
in Vision gives me
is
^._id
referring to the potential `section`s
_id
in this case?
The updated filter is:
^._id in *[_id==$document][0].primary[].section[]
Still not producing any results :'(
Hm, something tells me you're hitting some array travelsal bug in the query handling. I've seen similar queries failing due to that
aha
For what it's worth, it should be
^._id in *[_id==$document][0].primary[].section[]._ref
(you just want an array of strings)
Hmm… if I run
*["f6529b36-sectionID" in *[_id==$document][0].primary[].section[]._ref]
I’m just getting every single document in my store
Very odd
Because what you sent me does give me an array of strings
I would have assumed that
*["string" in []]
would produce the right results
But maybe there is a traversal bug of some sort
Have you ever tried to filter out a reference that’s already added to a list?
Would unique validation be of any helo? https://www.sanity.io/docs/array-type#unique()-49ee9907c730
user C
Just tested this to filter out refs already added to the list, seems to work:


          options: {
            filter: ({document}) => {
              const ids = document.people.map(person => person._ref).filter(Boolean)
              return {
                filter: '!(_id in $ids)',
                params: {ids}
              }
            }
          }
🙏
Hero! And all it took was some javascript magic! Thank you so much Jørn! Your second example was perfect… it actually checks the existence in more than one array!
I ♥️ Sanity
❤️
Hi still fiddelng with this is the code below allong the correct lines? Its not working at the moment?
export default {
	name: 'single',
	title: 'Single',
	type: 'document',
	fields: [
		{
			name: 'name',
			title: 'Name',
			type: 'string',
			validation: (Rule) => Rule.required(),
		},
		{
			name: 'tracks',
			title: 'Tracks',
			type: 'array',
			of: [
				{
					type: 'reference',
					to: [{ type: 'track' }],
				},
			],
			options: {
				filter: ({ document }) => {
					const ids = document.track.map((trk) => trk._ref).filter(Boolean)
					return {
						filter: '!(_id in $ids)',
						params: { ids },
					}
				},
			},
			validation: (Rule) => Rule.required(),
		},
	],
}
Think you're missing an s? Should be
document.tracks
?
Thanks good spot
Easy to miss 😅

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?