Simple search and filtering with Sanity
Adding search functionality to your project doesn’t have to be complicated. With your data in Sanity, you can filter content and return relevant results in just a few lines of code. This guide will outline the basic approach I use, and will hopefully provide the building blocks for you to adapt to your own requirements.
If you want to handle spelling errors and synonyms like a fuzzy search might, this won’t be the right solution for you. However, if all you need is basic text matching, the ability to pass in filter options, and boost certain results, this method will be more than suitable.
Here’s the full query, and then I’ll break it down:
const query = groq`*[_type == "post" && select(
length($author) == 0 => true,
author->name in $author
) && select(
length($category) == 0 => true,
count((categories[]->name)[@ in $category]) > 0
)] | score(
title match "*" + $input + "*",
boost(title match $input + "*", 2),
boost(title match $input, 10)
) | order(_score desc) {
...
} [_score > 0]`
const { data } = useSanityQuery(query, {
input,
title,
author,
category
})
First you need to filter by document type. For this example I’m using post
, and this would return all post
documents.
*[_type == "post"]
In this implementation, the select
function is used to enable conditional filtering, as they work like if...else
statements. To ensure all documents are returned by default, we define a filter condition that evaluates to true when its length is zero. This allows us to catch any documents that don’t match specific criteria and treat them as matches, providing a broad starting point for further filtering and processing.
select(
length($author) == 0 => true,
author->name in $author
)
The above is how you achieve this with a singular value, whereas below shows how to find a match within an array, with the addition of the count
function.
select(
length($category) == 0 => true,
count((categories[]->name)[@ in $category]) > 0
)
Next, the documents are ranked based on the text from a search input, using the score
and boost
functions. To keeps things concise, I’ve kept it just to match on the title
, but this could be repeated to cover any portion of text in the document.
The initial check is to see whether the input
is within the title
at all, with the possibility of other text being before or after. The score
is then doubled if the input
is at the start of the title
, as that will likely be a better match. Then if the input
is an exact match the score
is increased substantially, ensuring those documents rise to the top.
score(
title match "*" + $input + "*",
boost(title match $input + "*", 2),
boost(title match $input, 10)
)
Using the score
function adds a _score
value to each document, which allows us to order
the results, and also filter out all documents that don’t match at all.
order(_score desc)
[_score > 0]
For more query ideas and methods make sure to check out the documentation.