How to Write a Filter Query

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!

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?