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.

Development

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.

Let’s work together

Get in touch