How to do free text search in Portable Text field using GROQ?
Great question! To search within a Portable Text field in GROQ, you need to use the pt::text() function to convert the Portable Text to plain text, then combine it with the match operator and score() function. Here's how to do it:
*[_type == "discount"]
| score(pt::text(description) match $query)
| order(_score desc)
[_score > 0]Let me break down what's happening here:
pt::text(description)- This GROQ function extracts plain text from your Portable Text field, stripping away all formatting and structure. This is necessary because thematchoperator works on plain text strings.match $query- The match operator performs tokenized full-text search, breaking both your search term and the content into searchable tokens based on word boundaries.score()- The score() function computes relevance scores and adds a special_scorefield to each document. When the match expression evaluates to true, it assigns a score.order(_score desc)- Orders results by relevance, with the highest scores first.[_score > 0]- Filters out documents that didn't match at all (score of 0).
If you want to search across multiple fields with different priorities, you can use the boost() function to weight them differently:
*[_type == "discount"]
| score(
boost(title match $query, 3),
boost(pt::text(description) match $query, 1)
)
| order(_score desc)
[_score > 0]This would prioritize matches in the title field 3x higher than matches in the description field, as explained in this example about ordering search results.
The key insight is that you can't directly use match on Portable Text arrays - you need to convert them to plain text first with pt::text(). Before this function existed, developers had to manually traverse the block structure, but now it's built right into GROQ for exactly this kind of use case!
Show original thread11 replies
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.