How to select() from referenced values with multiple document types in GROQ?

5 replies
Last updated: Jul 14, 2020
boiling this down to the simplest form, is there a way I can
select()
from referenced values using GROQ?I have a reference which could refer to two possible document types. This query totally works – but it doesn’t seem like the “best” way to do it, and I’m not sure if there would be some unexpected behaviour outside of this isolated example, or in a more complicated case?

*[_type == "story" && !(_id in path('drafts.**'))] | order(publicationDate desc) {
	"references": references[] {
  	"title": reference-> name,
  	"title": reference-> title,
  }
}
AI Update

Yes! GROQ has a select() function that's perfect for this exact scenario. It's a much cleaner way to handle conditional field selection from references that could point to different document types.

Instead of your current approach (which does work but relies on duplicate field names silently overwriting each other), you can use select() like this:

*[_type == "story" && !(_id in path('drafts.**'))] | order(publicationDate desc) {
  "references": references[] {
    "title": select(
      defined(reference->name) => reference->name,
      defined(reference->title) => reference->title
    )
  }
}

The select() function evaluates conditions in order and returns the first truthy value. Each condition follows the pattern condition => value.

If you know the specific document types, you can be even more explicit:

*[_type == "story" && !(_id in path('drafts.**'))] | order(publicationDate desc) {
  "references": references[] {
    "title": select(
      reference->_type == "typeA" => reference->name,
      reference->_type == "typeB" => reference->title,
      "Untitled" // optional fallback
    )
  }
}

Why this is better than your current approach:

  • Explicit intent: Makes it clear you're handling different document types
  • More maintainable: Easier to understand and modify later
  • Scales better: Easy to add more document types or conditions
  • No hidden behavior: Your current approach works because GROQ allows duplicate keys in projections with the last value winning, but this is implicit behavior that could confuse other developers

The select() function stops evaluating as soon as it finds a matching condition, so it's also efficient. You can add as many conditions as you need, and always include a fallback value at the end for safety.

Show original thread
5 replies
I was expecting to do something a bit like this:
*[_type == "story" && !(_id in path('drafts.**'))] | order(publicationDate desc) {
	"references": references[] {
  	"title": select(
  		reference-> name => reference-> name,
  		reference-> title => reference-> title
		)
  }
}
oh also … the shortcoming of doing it the first way, I guess is that unnecessary conditions are searched for, met or not met, then overwritten … whereas using select would cut at first case?
How about something like:
*[_type == "story" && !(_id in path('drafts.**'))] | order(publicationDate desc) {
	"references": references[]->{
    _type == "someType" => {
      "title": name
    },
    _type == "otherType" => {
      title
    },
  }
}
How about something like:
*[_type == "story" && !(_id in path('drafts.**'))] | order(publicationDate desc) {
	"references": references[]->{
    _type == "someType" => {
      "title": name
    },
    _type == "otherType" => {
      title
    },
  }
}
oh amazing! thanks
user Z
. This is great – the second piece of the puzzle was checking if a property existed using
defined()
– having written this, I now feel like (probably falsely) like a certifiable GROQ wizard …

    "references": references[] {
      reference-> _type == "artist" => {
    		"title": reference-> name,
        "slug": reference-> slug.current,
    		"image": select(
    			defined(reference-> artworks) => reference-> artworks[0]-> {
          	...image,
  					"type": "artwork",
        		"metadata": image.asset->metadata
          },
          defined(image) => image {
       			...,
            "type": "override",
        		"metadata": asset->metadata
          },
        )
  		},
    	reference-> _type == "exhibition" => {
    		"title": reference-> title,
        "slug": reference-> slug.current,
        "image": select(
          defined(image) => image {
       			...,
          	"type": "override",
        		"metadata": asset->metadata
          },
  	        defined(reference-> images) => reference-> images[0] {
          	...,
  					"type": "installation",
        		"metadata": asset->metadata
          },
        )
  		},
  		_type == "external" => {
    		"title": title,
        link,
        "image": image {
       		...,
        	"metadata": asset->metadata
  			}
  		},
      _type == "pdf" => {
        "title": title,
        "file": file.asset->url,
        "image": image {
          ...,
          "metadata": asset->metadata
        }
      }
    }

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?