GROQ: Exclude already referenced documents from array reference field options

5 replies
Last updated: Feb 17, 2023
Hey y'all, looking some some GROQ guidance,I have a type named 'service', and want to be able to list sub-services as an array of references to other 'service' documents.

When adding a new sub service ("Type to search" or going through the drop down after clicking "Add item") I want to exclude any services already added to the sub service array from the available options.

I've checked the
cheat sheet & the GROQ docs, here's what I have so far - am I even close with this?
Note I'm using a dynamic filter her as I am also using other fields to reduce the number of options - this is simply a minimal example.

export const Service = defineType({
    name: 'service',
    title: 'Service',
    type: 'document',
    fields: [
        defineField({
            name: 'subServices',
            title: 'Sub Services',
            type: 'array',
            of: [
                {
                    type: 'reference',
                    to: [{ type: 'service' }],
                    options: {
                        // How do I exclude documents already added to the 'subServices' array?
                        filter: ({ document }) => {
                            return {
                                filter: '!(_id in subServices[]._ref)',
                            }
                        }
                    }
                }
            ],
        })
    ]
})
AI Update

You're very close! The issue is that you need to pass the actual subServices array values to the GROQ filter as parameters. Here's the corrected version:

export const Service = defineType({
    name: 'service',
    title: 'Service',
    type: 'document',
    fields: [
        defineField({
            name: 'subServices',
            title: 'Sub Services',
            type: 'array',
            of: [
                {
                    type: 'reference',
                    to: [{ type: 'service' }],
                    options: {
                        filter: ({ document }) => {
                            const subServiceIds = (document.subServices || [])
                                .map(ref => ref._ref)
                                .filter(Boolean);
                            
                            return {
                                filter: '!(_id in $subServiceIds)',
                                params: { subServiceIds }
                            }
                        }
                    }
                }
            ],
        })
    ]
})

The key differences from your code:

  1. Extract the IDs: You need to map through document.subServices to get an array of _ref values from the reference objects
  2. Use parameters: Pass the array as a GROQ parameter ($subServiceIds) rather than trying to reference the field directly in the query string
  3. Handle undefined: Add the || [] fallback and filter(Boolean) to handle cases where the array doesn't exist yet or contains undefined values

The GROQ filter string !(_id in $subServiceIds) means "exclude documents whose _id is in the subServiceIds array", which is exactly what you want.

This pattern works because the filter function receives the current document state, allowing you to dynamically adjust what references are available based on what's already been selected. The dynamic filter will re-evaluate each time the subServices array changes, keeping the available options up to date.

Show original thread
5 replies
I’ve used something like this in the past:
filter: ({ parent }) => {
  const existingEntries = parent
    .map(existingEntry => existingEntry._ref)
    .filter(Boolean);

  return {
    filter: `!(_id in $existingEntries) && !(_id in path('drafts.**'))`,
    params: {
      existingEntries,
    },
   };
},
Thank you
user M
, exactly what I needed 🙌Just curious, was my initial GROQ attempt a poor attempt at this query?
Perhaps I misinterpreted the syntax in the GROQ guides/cheatsheet?
No, you were basically there! This method just removes any possible null values from your existing entries and removes drafts so that you don’t see both the published and draft versions of the references.
Thanks for clarifying
user M
, I appreciate you taking the time to help me out with this.Have a great day
😃
You too!

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?