Multiple document queries in Sanity GROQ with conditional object structure

7 replies
Last updated: Jan 10, 2022
Hi All, I’m trying to fetch data with more than 1 query I found this answer and am referencing that answer. my query looks like this
export const frontPageSettings = `
*[_id in ["frontpage", "siteSettings"]]{
    _id == "frontpage" => {
        _id,
        pageDescription,
        pageTitle,
        pageBuilder,
        mainImage
    },
    _id == "siteSettings" => {
        title,
        description,
        primaryColor,
        secondaryColor,
        keywords
    }
}
` 
When I start up the Next app, I’m getting this error
error - ClientError: expected '}' following object body
Is there a better way to fetch data from more than 1 document with NextJS?
AI Update

I can see the issue with your query. The syntax you're using (_id == "frontpage" => { }) is conditional projection syntax, not a way to fetch multiple documents with different shapes. Let me explain the correct approach.

The Problem

Your query syntax is invalid because you're trying to use conditional projections incorrectly. The => operator in projections is meant to conditionally add fields to an object, not to create entirely different object structures based on filters.

The Solution

To fetch multiple documents with different structures in one query, you have a few options:

The cleanest approach is to make two separate queries. In Next.js, you can fetch them in parallel:

// In your Next.js component or page
const [frontpage, siteSettings] = await Promise.all([
  client.fetch(`*[_id == "frontpage"][0]{ _id, pageDescription, pageTitle, pageBuilder, mainImage }`),
  client.fetch(`*[_id == "siteSettings"][0]{ title, description, primaryColor, secondaryColor, keywords }`)
])

Option 2: Fetch Both and Transform Client-Side

Fetch both documents and project all fields, then handle the different structures in your application:

export const frontPageSettings = `
  *[_id in ["frontpage", "siteSettings"]]{
    _id,
    // frontpage fields
    pageDescription,
    pageTitle,
    pageBuilder,
    mainImage,
    // siteSettings fields
    title,
    description,
    primaryColor,
    secondaryColor,
    keywords
  }
`

Then in your code, separate them:

const data = await client.fetch(frontPageSettings)
const frontpage = data.find(d => d._id === 'frontpage')
const siteSettings = data.find(d => d._id === 'siteSettings')

Option 3: Use Conditional Projections (Correctly)

If you want to use conditional projections, the syntax needs to be different. The condition goes inside the projection to conditionally add fields:

export const frontPageSettings = `
  *[_id in ["frontpage", "siteSettings"]]{
    _id,
    _id == "frontpage" => {
      pageDescription,
      pageTitle,
      pageBuilder,
      mainImage
    },
    _id == "siteSettings" => {
      title,
      description,
      primaryColor,
      secondaryColor,
      keywords
    }
  }
`

This will give you objects where frontpage documents have their specific fields and siteSettings documents have theirs, but they'll all have _id at the root level.

Why Your Original Query Failed

According to the GROQ pipeline components documentation, the condition => {} syntax expands the object on the right-hand side into the projection if the condition is true. It doesn't replace the entire projection - it adds to it. Your syntax was trying to use it as a filter replacement, which isn't valid.

Best Practice

For your use case with Next.js, Option 1 (separate queries with Promise.all) is the cleanest and most maintainable approach. It's explicit about what you're fetching, easier to type with TypeScript, and the performance difference is negligible since the queries run in parallel.

Show original thread
7 replies
Yep, that did it, thank you!
follow up question to this, when I fetch data like this, it returns a lot of my fields as arrays that just read
[[object], [object]]
and it seems like it can’t actually read those objects, how would I go about this?
That usually means that you're using a promise-based value that hasn't resolved yet. Did you maybe forget an
await
somewhere?
I don’t think so, my getServerSideProps has the
await
in it. For reference this also happened when I was fetching a _type without specifying index 0 or
[0]
after my type call
export const getServerSideProps = async function (context) {

  const data = await client.fetch(frontPageSettings)

  return {
    props: { data },
  }
}
Here’s my getServerSideProps
export const getServerSideProps = async function (context) {

  const data = await client.fetch(frontPageSettings)

  return {
    props: { data },
  }
}
Here’s my page component

export default function Home({ data }) {
  console.log(data)
  return (
    <></>
  )
}
And this is what my console is logging

[
  {
    _id: 'frontpage',
    mainImage: { _type: 'image', asset: [Object] },
    pageBuilder: [ [Object], [Object], [Object], [Object] ],
    pageDescription: [ [Object] ],
    pageTitle: 'Frontpage'
  },
]
Your server/terminal console is not going to drill down into objects, but you can “expand” those objects using something like this:

console.log(JSON.stringify(data, null, 2))
As for using that data on your page, you’ll need to specify keys on objects (e.g., if
person
is an object, you can’t use that on the page—you’d need to specify
person.name
, as an example) and map through arrays (e.g., if
people
is an array, you’ll need to map through it and handle each
person
). Both of those are seen here, where
categories
is an array and each
category
is an object with at least
_id
and
title
keys on it:

<ul>
  {categories.map(category => (
    <li key={category._id}>{category.title}</li>
  ))}
</ul>
You’ll probably want to render
categories
conditionally (check if it’s empty and if it is, don’t try to map over it).

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?