This is a quick tip on how to truncate a Sanity block field to create an excerpt using GROQ.
When querying the field using a Sanity Client and GROQ, there is no inbuilt excerpt function, or even a truncate function, so we need to do the steps ourselves.
Truncating the field could be done using a JavaScript function after the query, but this would require the entire field to be returned, which is not ideal.
This bit of a hack and not the most efficient query, but essentially:
- Get the string of the body as above using the text feature of the portable text functions.
- Split the string into an array of every character.
- truncate it to 255 characters by taking a subset of characters (assuming it's actually longer than 255 characters).
- Join every remaining character back together.
- Add an ellipsis to the end.
The queries
Get the string of the portable text block field using the text function of the portable text:
1pt::text(theField)
Because pt::text
is used, it will return the text of the field, but not the formatting. This means that if you have a block field with a heading, it will return the heading text, but not the heading formatting.
Split the string into an array of every character using the split function of strings:
1string::split("the string", "")
Truncate the characters to 255 characters by taking a subset of characters (assuming it's actually longer than 255 characters):
1theArray[0..255]
Join an array of characters back together using the join function of arrays:
1array::join(["t", "h", "e", " ", "s", "t", "r", "i", "n", "g"], "")
We are also concatenating the ellipsis to the end of the truncated string:
1+ "..."
Examples
Because GROQ queries can be piped together, we can do all of this in separate queries.
In the following example I will use a field called body
which is a Sanity block field. Replace that as needed.
Joined together it can either be a set of queries piped together:
1*[_type == "article"] {
2 "excerpt": (pt::text(body)), // 1
3 } | {
4 "excerpt": string::split(excerpt, "") // 2
5 } | {
6 "excerpt": excerpt[0..255] // 3
7 } | {
8 "excerpt": array::join(excerpt, "") // 4
9 } | {
10 "excerpt": exceprt + "..." // 5
11 }
Or as one query:
1*[_type == "article"] {
2 "excerpt": array::join(string::split((pt::text(body)), "")[0..255], "") + "..."
3}
Issues
This is a hack, and does have some downsides for example:
- If the last character happens to be a
.
then there are 4 characters in the ellipsis:....
. - The string is split by character, so if a word is split in half, it will be split in half.
- You could split on a space
" "
to do this by word, but this doesn't account for other punctuation, and there's no easy way to truncate to a specific number of character. - If the string is less than 255 characters, it will still return the entire string.
- Because tags and markup is removed by
pt:body()
the returned value is just a string with no markup such as paragraphs or titles.
See the GROQ excerpt on the Sanity.io Exchange community