The storefront is a fashion shop where shoppers browse collection pages and Algolia InstantSearch (the React UI that reads hits from the search index) paints the product grid. Merchandisers run campaigns in Sanity CMS, a headless content studio where they pin hero SKUs, hide slow sellers, and boost looks for a week. One merchandiser pinned a hero SKU, saved twice, refreshed the PLP, and the grid still ranked like nothing happened. InstantSearch took the blame in Slack. Groq, Sanity's query language over stored documents, had returned zero rows the whole time.
The shopper saw default Algolia ranking. The merchandiser stared at a filled Sanity record with locale set and pins attached. I had a pipeline that claimed to apply merchandising rules and then quietly applied nothing. Hits still came back. They were just the wrong hits, which is quieter than an empty index and much harder to debug in a hurry.
The desk heading is decoration
Sanity schema types carry an id and a title. title is what the desk shows, something friendly like "Collection merchandising." id is what Groq sees, such as collectionMerch, whatever I registered in defineType. I had written *[(_type == "Collection merchandising") && locale == $locale] and got zero hits every time. The documents sat in the desk with pins filled and locale set, looking like they should already be on the index.
That filter looks reasonable from the Studio. I opened the type, read the heading, pasted the heading into Groq because English matched English. Groq does not index the heading. _type is the schema id. The title can change when someone wants a nicer label. The id is the contract between your schema and every query downstream. I had been debugging InstantSearch ranking while the merch pipeline never left Sanity.
Groq only sees the schema name
I stopped matching the English label and matched the schema id instead. The same documents that were "missing" showed up on the first run. The string I put in defineType({ name: 'collectionMerch' }) is _type. The string in the desk heading is decoration for humans.
*[_type == "collectionMerch" && locale == $locale]{
_id,
collectionHandle,
pinned[]->{ sku },
hidden[]->{ sku }
}When I need the label in the UI, I read title from the schema, not from _type. Mixing those two is how I got a pretty empty grid while the merchandiser kept filing tickets against Algolia.
Pins belong on the index, not in InstantSearch
After Groq returns rows, the server writes those pins onto the Algolia index (or a replica) with the admin client. InstantSearch on the storefront keeps using the search-only key and never talks to Sanity. That split is the point: merchandising is an index-time job, search is a read, and the browser should not Groq CMS documents on every collection view. If InstantSearch looks wrong, I check whether the rule ever reached the index before I rewrite the hit component.
If I rename the schema name, every Groq filter that used the old id goes silent the same way the title filter did. I rename titles freely and treat name like an API. A noisy Studio heading beats a silent empty _type at three in the morning.
The Studio is a UI. Groq is a query language over documents. I keep confusing them when the heading is prettier than the id, and merchandisers pay for that confusion in unpinned heroes.
Happy coding!
Sander