Custom GROQ functions
Learn how to create your own GROQ functions.
Sometimes you find yourself repeating the same portion of a GROQ query across multiple queries, or even within a single complex query. Custom functions for GROQ allow you to create modular, reusable sub-queries.
Prerequisites:
- Custom GROQ functions are available on all API versions except v1.
Function anatomy
Custom functions look similar to other GROQ functions, but with some limitations. They include a namespace and accept a parameter. Let's look at an example function that follows a reference and returns a projection that combines an author's first and last name.
fn ex::name($author) = $author-> { "name": firstName + " " + lastName }; *[_type == 'post']{ "author": ex::name(author) }
All functions start with the fn
keyword and contain a namespace, name, parameter, and function body. In the example above:
ex
is the namespace.name
is the function name.$author
is the parameter.$author-> { "name": firstName + " " + lastName }
is the body.
Custom function declarations must happen at the start of the GROQ query and each declaration must end with a semicolon (;
). You can use them anywhere you'd normally send a GROQ query, such as a Sanity client, the HTTP query API, or the Vision tool. For example, in @sanity/client
:
const QUERY = ` fn ex::name($author) = $author-> { "name": firstName + " " + lastName }; *[_type == "post"]{ "author": ex::name(author) }` const posts = await client.fetch(QUERY)
See the GROQ functions reference for additional details.
Examples
Custom functions support a limited set of formats at this time:
$param{...}
$param->{...}
$param[]{...}
$param[]->{...}
Let's use the following documents as an example to explore each format. There is a person
document, an occupation
document, and two pet
documents.
{ "_id": "a", "_type": "person", "name": [ { "first": "Jane", "last": "Doe" } ], "age": 99, "occupation": { "_ref": "developer" }, "belongings": [ {"name": "laptop"}, {"name": "badge"}, {"name": "backpack"} ], pet: [ {"_ref": "dog"}, { "_ref": "dog2" } ] }
{ "_id": "developer", "_type": "occupation", "title": "Software Engineer" }
{ "_id": "dog", "_type": "pet", "name": "Pookie" }
{ "_id": "dog2", "_type": "pet", "name": "Snookie" }
Basic projection
First we'll define a function that returns a basic projection. This function, ex:: details
, takes a $person
parameter and returns a projection containing their name
and age
. To use the function, we pass in @
to represent the person returned by the filter.
fn ex::details($person) = $person{name, age}; *[_type == "person"] { "info": ex::details(@) }
[{ "info": { "age": 99, "name": { "first": "Jane", "last": "Doe" } } }]
Follow references
It's common to follow references to include part or all of their contents in the referencing object. This function follows the person's occupation
reference and returns a projection with their title.
fn ex::title($ref) = $ref->{title}; *[_type == "person"] { "occupation": ex::title(occupation) }
[{ "occupation": { "title": "Software Engineer" } }]
Array projection
This function iterates through the person's belongings
to display their names.
fn ex::items($arr) = $arr[]{name}; *[_type == "person"] { "stuff": ex::items(belongings) }
[{ "stuff": [ {"name": "laptop"}, {"name": "badge"}, {"name": "backpack"}, ] }]
Array of references
This function follows each reference in the person's pet
key.
fn ex::pets($items) = $items[]->{name}; *[_type == "person"] {"pet": ex::pets(pet)}
[{ "pet": [ {"name": "Pookie"}, {"name": "Snookie"} ] }]
PTE blocks
Reusing the logic for parsing PTE blocks is a common use-case for functions. This example parses a set of blocks regardless of the incoming blocks.
fn ex::blocks($arr) = $arr { ..., _type == 'docsCallout' => { ..., content[] { ..., markDefs[] { ..., _type == 'link' => { isInternal, _key, _type, reference->, url }, _type == 'acronym' => { _key, _type, value }, // etc } } } }; *[_type == "article"] { _id, title, "slug": slug.current, "content": ex::blocks(content[]), "description": ex::blocks(description[]) }
Limitations
At this time, functions are limited to the formats displayed above. Additionally, custom functions do not yet support:
- Recursion.
- Accessing the parent scope.
- Passing multiple parameters.
- Accessing the function parameter more than once in the function body.
- TypeGen does not support custom functions. At this time, use the
// @sanity-typegen-ignore
comment before the function to exclude it from TypeGen, then manually create types. See the ignoring individual queries example.
Was this page helpful?