Merging locale documents with GROQ fallback values

10 replies
Last updated: Oct 29, 2021
hey, anyone know if it’s possible to merge documents with GROQ? use case is merging separate locale documents onto the main/fallback locale, so for example if we had an EN and DE document any DE fields with values would be merged into the EN but anything missing from DE would just have the EN values
AI Update

Yes, you can definitely merge documents with GROQ! For your locale merging use case, you'll want to use the object spread operator (...) combined with coalesce() to handle fallbacks.

Here's the basic pattern for merging a DE locale document onto an EN fallback:

*[_id == $documentId][0] {
  ...,
  "title": coalesce(^[_id == $documentId + ".de"][0].title, title),
  "description": coalesce(^[_id == $documentId + ".de"][0].description, description)
}

But a cleaner approach is to fetch both documents and merge them in the projection:

{
  "en": *[_id == "en-doc"][0],
  "de": *[_id == "de-doc"][0]
} | {
  ...en,
  ...select(defined(de) => de)
}

Or for a more complete solution that merges all fields automatically:

*[_id == "en-doc"][0] {
  ...,
  ...coalesce(*[_id == "de-doc"][0], {})
}

The key operators you'll use:

  • ... (spread operator): Expands objects into the surrounding projection. When you use {...a, ...b}, properties from b will override matching properties from a
  • coalesce(): Returns the first non-null value, perfect for fallbacks. For example, coalesce(de.title, en.title) returns the DE title if it exists, otherwise the EN title
  • select(): Can conditionally include values based on whether they're defined

The spread operator is documented in the GROQ operators spec, and you can read more about coalesce() in the GROQ functions documentation.

This approach keeps your query logic clean and handles missing fields gracefully by falling back to your EN values automatically.

Show original thread
10 replies
Hey User! You'll probably want to use the
coalesce()
GROQ function here. This will allow you to specify what's returned from GROQ if it comes across a null value. I can give more specific guidance if you share an example of the schema for these documents!
hey racheal, thanks!
to keep it simple, for these docs:

{ _id: 1, _type: "foo", lang: "en", title: "hello world", body: "something in english" }
{ _id: 2, _type: "foo", lang: "de", title: "hallo welt" }
{ _id: 3, _type: "foo", lang: "es", title: "hola mundo" }
i’d want the desired output for a query like
*[_type == "foo" && _id == 3 && lang == "es"]
to be:
{ _id: 3, lang: "es", title: "hola mundo", body: "something in english" }
Got it! In that case, the following should work for you:
*[_type == 'foo' && _id == 3 && lang == 'es']{
  _id,
  title,
  'body': coalesce(body, *[_type == 'foo' && _id == 1 && lang == 'en'].body)
}
ok, so every field would need its’ own subquery? that seems like it would be expensive if there were say a dozen fields… i suppose another way would be to just do a subquery for the whole doc and have the consumer merge in whatever fields have values
If they're combined into a single query with subqueries it will only be counted as a single query. You also shouldn't see a performance issue as GROQ can take a beating before lagging. That said, a common practice is to use something like the international input plugin to store translations within the same document. It may make it easier for you to handle missing fields on the frontend.
good to know thanks. i’ll take another look at liam’s plugin… any chance the sanity team will adopt/maintain that plugin or something similar in the future?
Ah, good question. I guess you could say there's a non-zero chance that could happen. It's a topic of internal discussion right now: when these plugins get created and adopted, how/when/who should be the one to maintain them? I'm sorry that's a bit vague, but a course of action hasn't been decided yet.
no worries, cant do everything all at once 🙂 glad to know the discussion is happening
thanks so much for all your help
Very happy to help!

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?