Troubleshooting search functionality issues in Sanity.io

13 replies
Last updated: Dec 6, 2021
I'm also experiencing an issue with the Search Functionality within Sanity where the search request is not returning anything. the
console
shows a 400 error alongside some sort of invalid character issue, wondering if anyone has encountered something similar & managed to fix it? 😄
AI Update

I've seen this issue before! A 400 error with "invalid character" in Sanity search is usually caused by special characters in your search input that aren't properly handled when building GROQ queries. Let me walk you through what's happening and how to fix it.

The Root Cause

When you're building search queries dynamically (especially with the match operator), characters like quotes (" or '), backslashes (\), or other special characters can break your GROQ query syntax if you're using string interpolation:

// ❌ This breaks if searchTerm contains quotes or special chars
const query = `*[_type == "post" && title match "${searchTerm}"]`

// User types:  demo"test
// Results in:  *[_type == "post" && title match "demo"test"]
// → 400 error: invalid character

The Solution: Use GROQ Parameters

The best and safest approach is to use GROQ parameters instead of string interpolation. Parameters are automatically escaped by the Sanity client, preventing syntax errors:

// ✅ Safe approach using parameters
const query = `*[_type == "post" && title match $searchTerm]`
const params = { searchTerm: `*${userInput}*` }

const results = await client.fetch(query, params)

With parameters, the Sanity client handles all the escaping for you, so special characters won't break your query.

Understanding the match Operator

One important thing to know: the match operator works on tokenized text, not regex patterns. It breaks text into searchable words based on word boundaries and special characters. For example, "user@sanity.io" is tokenized as ['user', 'sanity', 'io'].

This means:

  • You don't need to escape regex characters for match to work correctly
  • Special characters like @, ., / act as word separators
  • Wildcards (*) are supported for prefix matching

Here's a robust implementation:

import { client } from './sanityClient'

async function search(userInput) {
  const sanitized = userInput.trim()
  
  if (!sanitized) return []
  
  // Using parameters (recommended)
  const query = `*[_type == "post" && title match $searchTerm] | order(_createdAt desc)`
  const params = { searchTerm: `*${sanitized}*` }
  
  try {
    return await client.fetch(query, params)
  } catch (error) {
    console.error('Search error:', error)
    return []
  }
}

Debugging Steps

  1. Check the Network tab - Look at the exact GROQ query being sent in your browser's developer tools
  2. Test with simple input - Try searching with just letters first, then gradually add special characters to identify which one causes the issue
  3. Check your query construction - If you're using template literals with ${}, switch to GROQ parameters with $variableName

If You're Using the HTTP API Directly

If you're calling the HTTP API directly (not through the JavaScript client), make sure your query is properly URL-encoded:

const encodedQuery = encodeURIComponent(query)

The JavaScript client handles this automatically, which is another reason to use parameters!

Let me know if you're still seeing the 400 error after switching to parameters - it would help to see the exact query that's failing in your Network tab. The error message should also show which character is causing the issue.

Hi User. We’ve seen it once recently. It came down to the use of brackets to access array elements in a preview rather than our required dot notation . Unfortunately, it broke search but didn’t prevent building the studio. I would start by searching your code base for a preview using something like:

obj.body[0].children[0].text
and changing it to something (in this example) like:


obj.body.0.children.0.text
The best approach is probably to search for
preview: {
.
If that’s
not the case, please let us know.
Thanks, User. So would I look for that in the
select: {
part or
prepare()
or either?
In select.
Thanks, User. So would I look for that in the
select: {
part or
prepare()
or either?
In select.
Thanks, User. So would I look for that in the
select: {
part or
prepare()
or either?
In select.
Can't seem to find anything 😞Wonder would it have something to do with me having
"__experimental_spaces"
spaces enabled?
I don’t think that would cause this, but you never know.
Would you be willing to share your schema for us to take a look? The whole studio folder (minus node_modules) can be zipped and DMed.
Sure thing, I'll DM it to you now
Sure thing, I'll DM it to you now
Turned out to be a space in a
preview.select
value. I will log a feature request so that the errors returned are clearer (and help point the developer in the right direction).
Turned out to be a space in a
preview.select
value. I will log a feature request so that the errors returned are clearer (and help point the developer in the right direction).

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?