Using GROQ queries in Structure Builder for custom ordering

11 replies
Last updated: Aug 23, 2022
If I am using the Structure Builder, I see that there are filters, but it is quite explicit that I cannot feed it queries.
I wanted to take second pane child documents of my first pane's schema type, and apply a very particular order to them, but custom orderings depend on a string, which I don't have (I am doing some basic math to a nested value inside the color picker).

I thought about making a custom input that tried grabbing that and returning a string out of that to feed into the orderings object, but I'd much rather use a GROQ query ( with an
order qualifier at the end ) because that more or less returns the list I am after.
Is this possible? I get a bit faceblind with the terminology like list, list item, listtype, listtypeitem, etc. and while
user B
posted a useful example to pick a child at random, it's just the one item and my attempts to come up with a map -- even though the console logs the full list properly -- don't work. I can try to reproduce the explosion if someone needs exact errors but I'm not even sure if it's possible.
AI Update

Looking at your question, you're trying to use GROQ's order() clause within Structure Builder's documentList() to sort child documents based on custom calculations (like math operations on nested color picker values). The key issue is that Structure Builder's filtering system has limitations.

The short answer: No, you cannot use GROQ's order() clause directly in Structure Builder filters. However, there are two viable approaches to achieve custom ordering:

The Structure Builder's defaultOrdering() method requires a field name string, not a GROQ expression. The solution is to compute your ordering value and store it as a field in your document:

// In your schema
{
  name: 'computedOrderValue',
  type: 'number',
  hidden: true, // Hide from editors if it's just for ordering
  // Calculate this value when the color picker changes
}

Then use it in Structure Builder:

S.documentList()
  .title('Ordered Projects')
  .filter('_type == "myType"')
  .defaultOrdering([
    {field: 'computedOrderValue', direction: 'desc'}
  ])

You can compute this field using:

  • A custom input component that calculates and sets the value when the color picker changes
  • Document actions that compute it on save
  • Sanity Functions with event handlers that react to document changes and update the field automatically

Option 2: Custom component view with GROQ

If pre-computing isn't feasible, create a custom component that fetches and displays documents with full GROQ ordering:

S.listItem()
  .title('Custom Ordered List')
  .child(
    S.component(({documentStore}) => {
      const [docs, setDocs] = useState([])
      
      useEffect(() => {
        const query = `*[_type == "myType"] | order(
          color.hsv.v * color.hsv.s desc
        )`
        
        documentStore.getClient().fetch(query)
          .then(setDocs)
      }, [])
      
      return (
        <div>
          {docs.map(doc => (
            <DocumentListItem key={doc._id} doc={doc} />
          ))}
        </div>
      )
    })
  )

Why the limitation exists

As noted in the Structure Builder reference, the filter() method "does not support joins, since they operate on individual documents, and will ignore order-clauses and projections." This is because Structure Builder uses realtime listeners that need to efficiently determine which documents match the filter as changes occur.

The defaultOrdering() method works differently—it tells the UI how to sort the already-filtered results, but it can only reference actual document fields, not computed expressions.

About the terminology confusion

You mentioned getting "faceblind" with terms like list, listItem, listType, etc. Here's a quick breakdown:

  • list() - Creates a pane containing multiple items
  • listItem() - A single clickable item within a list
  • documentList() - A specialized list that shows documents matching a filter
  • documentListItem() - A single item representing a document

The Structure Builder reference has the full breakdown of each method.

Bottom line: Pre-compute your ordering value into a document field, then reference it in defaultOrdering(). This gives you the full power of custom calculations while working within Structure Builder's constraints.

A
child()
can actually take a function. Even better, it can take an async function, which will allow you to use a GROQ query to get the documents you want! There's an example of this here . You can just tweak the query and use it! Make sure you install rxjs so that your lists will automatically include any new documents you create.
Wow,
user M
that was lightning fast! Thanks! I'll give it a shot.
Huzzah, a finished product: https://somup.com/c3j0VxUeIC
You really can do anything in :sanity: anity, it seems. In Star Trek : The Next Generation there was an episode that (spoilers) had them discover one base humanoid race that everyone supposedly came from, kind of as a way to explain why every new life form they discovered wasn't really much different and always somehow managed to resemble people covered in makeup and foam 😃
Sanity is like we're doing things backwards. This is such a raw, pure form of content creation that you'd think it was first and everyone else was a derivative that came after, just because it seems so direct and capable in comparison to, well,
literally everything else.
Instead it is here, fresh and modern like a vanguard, saving us from the perils of the past like an S-shaped angel!
😛
Ahh, I love getting to see inside the Studios people build, so thank you for sharing! We need a Sanity :sanity: with angel wings on it!
it's beautiful
:sanity-wings:
Now that is beautiful!
I give it 5 out of 5 :knut: :knut: :knut: :knut: :knut: s
petitioning to make this an official standard of rating stuff in sanity!
"My god! It's a 5 on the :knut: scale!"

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?