
Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag storeLooking at your query, you're on the right track! Here are several improvements to help you implement the category/tag matching functionality you're looking for:
in operator for array matchingThe key to matching categories loosely (like "Food Bank" matching "Food Banks") is using the in operator to check if your search term exists within category/tag arrays:
export const search = groq`*[
_type in ["post", "service"] && (
// Text matching
title match "*" + $query + "*" ||
(defined(body) && pt::text(body) match "*" + $query + "*") ||
(defined(description) && description match "*" + $query + "*") ||
// Category matching (if categories is an array of references)
$query in categories[]->title ||
// Tag matching (if tags is an inline array)
$query in tags[]
)
]{
_type,
title,
_type == "post" => {
body,
categories[]-> {
title,
slug
}
},
_type == "service" => {
description,
categories[]-> {
title
}
}
}To handle loose matching and rank results by relevance, use the score() function:
export const search = groq`*[
_type in ["post", "service"] && (
title match "*" + $query + "*" ||
(defined(body) && pt::text(body) match "*" + $query + "*") ||
(defined(description) && description match "*" + $query + "*") ||
$query in categories[]->title ||
$query in tags[]
)
] | score(
boost(title match "*" + $query + "*", 3),
boost($query in categories[]->title, 2),
boost(pt::text(body) match "*" + $query + "*", 1),
boost(description match "*" + $query + "*", 1)
) | order(_score desc) {
_type,
_score,
title,
...
}`This scores exact title matches highest, category matches second, and body/description matches lower.
The match operator tokenizes text, so "Food Bank" will match documents containing "Food" and "Bank" separately. However, for better category matching, you might want to:
title match $query + "*" will match "Food Bank" to "Food Banks"*[
_type in ["post", "service"] && (
title match $query + "*" ||
categories[]->slug.current match $query + "*" ||
categories[]->title match $query + "*"
)
]_type in ["post", "service"] instead of separate OR conditionsdefined(body) before accessing fields that might not exist$query in categories[]->title for reference arrays or $query in tags[] for inline arraysscore() functionorder(_score desc) to show best matches firstThe in operator is perfect for your use case - it checks if a value exists anywhere in an array, whether it's an inline array of strings or an array of references to other documents. This will help you match "Food Bank" queries to posts/services in the "Food Banks" category!
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.
Content operations
Content backend


The only platform powering content operations
By Industry


Tecovas strengthens their customer connections
Build and Share

Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag store