How to resolve references in GROQ queries for page builder components

10 replies
Last updated: May 31, 2022
Hi! I have a question about
GROQ
and
references

I have this query:

*[_type == "page" && pageMetadata.slug == 'test'][0]{
    pageBuilderComponents
}
It returns an array. The problem is that
some (but not all) of these elements are references, while the rest are objects. I would like to resolve the references.
I have tried
pageBuilderComponents[]->
. This will resolve the references correctly, but those elements which were already objects, will now become
null
.
How can I resolve only those elements that are references?
AI Update

Perfect! The article actually covers your exact use case. When you have an array with mixed content (some references, some inline objects), you can use the coalesce() function with the @ operator.

Here's the solution:

*[_type == "page" && pageMetadata.slug == 'test'][0]{
  pageBuilderComponents[]{
    ...coalesce(@->, @)
  }
}

How this works:

  • @ represents the current element in the array
  • @-> attempts to dereference the current element (only works if it's a reference)
  • coalesce(@->, @) tries to resolve as a reference first (@->), and if that returns null (because it's not a reference), it falls back to the original object (@)
  • The spread operator ... expands all the fields from whichever result coalesce returns

This pattern is mentioned in the Reference Access Operator documentation as the recommended approach for handling mixed content types in arrays.

Alternative approach if you want more explicit control:

*[_type == "page" && pageMetadata.slug == 'test'][0]{
  pageBuilderComponents[]{
    _type,
    select(
      defined(_ref) => @->,
      !defined(_ref) => @
    )
  }
}

This uses select() to check if the element has a _ref field (indicating it's a reference) and conditionally resolves it. However, the coalesce approach is cleaner and more idiomatic for this use case.

What does the schema look like? Are the objects inside the pageBuilderComponent or alongside it?
If inside, you may want to list them separately, dereferencing the references along the way but leaving the others untouched.

It may be the case as well that if you call out for the deference-able ones specifically, you can add an ellipsis to catch the rest.
It is inside yes. The Schema looks like this:

export default {
    name: 'pageBuilderComponents',
    title: 'Page builder Components',
    type: 'array',
    of: [
      { type: 'component1' },
      { type: 'component2' },
      { type: 'component3' },
    ]
  };
When you say list them separately, what do you mean? Do you mean this:
1. First do one GROQ query to get to full array
2. Split it into two lists where one contains only references and contains only objects
3. Do another GROQ query to resolve the references
Or do you mean listing them as two separate arrays in the schema? I would prefer to have it as one array there.
I meant if there was a page document type as indicated in the original query that, if there were other things besides the page builder array field in the same level as it.
In this case all three of those being references to a different type means that you will need to decide what about those particular referenced types you want to fetch.

If they have their own separate schema or naming conventions you could try an ellipsis to ask for all of it, otherwise first make a condition and then say what to do for each condition.

Have you had a chance to read the documentation on conditions on the GROQ cheat sheet?
I think you want to use the
coalesce
function:
*[_type == "page" && pageMetadata.slug == 'test'][0]{
    pageBuilderComponents[]{
      'component': coalesce(@->, @)
  }
}
Oh, that's smart. Coalesce being the one that returns the first non-null value, and the @ sign referencing the root value of the scope (in this case, I believe, whatever the 'each' item is in the 'component' projection, and the -> for dereferencing when applicable. Does that sound about right?
So in plain language, "For each component, if there's something to dereference, grab me that and return the inside bits. If not, try to just grab what's there directly."
user S
exactly!
That worked perfectly, thanks! And thanks for the explanation User 🙏
I realised now that I also need it to recursively resolve references to an unknown depth, as a reference can contain another reference.
From my research, it looks like this is not possible yet in GROQ, is that correct? If so, I guess I would have to set up GraphQL for that instead.
You can’t currently resolve references to a certain depth, unfortunately. Are there page components inside of your page components?
Yes

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?