Functions

GROQ function reference

Functions in GROQ take a set of arguments of specific types and return a single value of a specific type. They may be polymorphic, i.e., accept several argument type variations possibly returning different types, and may take a variable number of arguments. Function calls return null if arguments have invalid types, and an error if the function does not exist.

Function namespaces

Namespaces allow for a stronger grouping of functionality within the GROQ specification. They create dedicated scopes for global functions, as well as safer distinctions for specific implementations of GROQ.

When using GROQ to query, Sanity Content Lake has a few key namespaces:

  • The global namespace – all the base GROQ functions
  • The pt namespace – specific functions pertaining to Portable Text
  • The geo namespace – functions for managing and querying against geolocation
  • The sanity namespace – functions for querying against the current project environment

Accessing functions in a namespace

All functions exist within a namespace and can be accessed via a call to the function with a prefix a string of the namespace name, followed by two colons and then the function name.

// The pt namespace contains functions related to Portable Text
// This function returns a plain text version of a Portable Text object
pt::text(ptNode)

// The geo namespace contains functions related to geolocation
// This function returns true if the second argument is fully contained in the first
geo::contains(polygon, point)

Global functions

Functions that exist for all implementations of GROQ exist in the global namespace. They can be accessed without using the namespace string.

// Non-namespaced
references('someId')
// equates to
global::references('someId')

coalesce

coalesce(<any>...) <any>

Takes a variable number of arguments of any type and returns the first non-null argument if any, otherwise null - e.g., coalesce(null, 1, "a") returns 1.

// If title.es exists return title.es
// Else return title.en
// If neither exist, return null
*[_type == "documentWithTranslations"]{
  "title": coalesce(title.es, title.en)
} 


// If rating exists, return rating,
// Else return string of 'unknown'
*[_type == 'movie']{
  'rating': coalesce(rating, 'unknown')
}

count

count(<array>) <integer>

Returns the number of elements in the passed array, e.g. count([1,2,3]) returns 3.

// Returns number of elements in array 'actors' on each movie
*[_type == 'movie']{"actorCount": count(actors)} 

// Returns number of R-rated movies
count(*[_type == 'movie' && rating == 'R']) 

dateTime

dateTime(<string>) <datetime>

Accepts a string in RFC3339 format (e.g. 1985-04-12T23:20:50.52Z) and returns a DateTime. This is also the format used in the _createdAt and _updatedAt fields. Typically used to let GROQ know to treat a string as a date, especially useful when you need to compare them or perform time arithmetic operations.

Subtracting two DateTimes returns the number of seconds between those time stamps. Adding a number to a DateTime returns the DateTime that amount of seconds later (or earlier if the number is negative).

*[_type == "post"]{
  title,
  publishedAt,
  "timeSincePublished": dateTime(now()) - dateTime(publishedAt)
}

Protip

You can create RFC3339-dateTime strings in JavaScript with the Date.prototype.toISOString() method.

defined

defined(<any>) <boolean>

Returns true if the argument is non-null, otherwise false.

// Returns all documents if awardWinner has any value (of any type)
*[defined(awardWinner)] 

identity

identity() <string>

Returns the identity (user ID) of the user performing the current action, or the special values <anonymous> for unauthenticated users and <system> for system-initiated actions.

length

length(<array|string>) <integer>

Returns the length of the argument, either the number of elements in an array or the number of Unicode characters in a string, e.g., length([1,2,3]) returns 3, and length("Hi! 👋") returns 5.

// Return posts with more than 2 authors
*[_type == "post" && length(authors) > 2]{
  title,
  authors[]->{
    name
  }
}

Gotcha

While length() works on arrays, you should consider using count() as it's optimized for arrays.

lower / upper

lower(<string>) <string>

upper(<string>) <string>

The lower() and upper() functions take a string and return back the string in all lowercase characters or all uppercase characters.

*{
  "upperString": upper("Some String"), // Returns "SOME STRING"
  "lowerString": lower("Some String")  // Returns "some string" 
}

now

now() <string>

Returns the current time in RFC3339 format with microsecond resolution in the UTC time zone, e.g., 2021-08-19T15:51:24.846513Z. The current time is stable within an operation such that multiple calls return identical values. This generally refers to the start time of the operation except for listener queries. This refers to the event's transaction time.

Caching now() in APICDN

Using now() and our APICDN creates a conundrum: how long should now() be cached?

We have created a special caching rule that says now() is only valid for 120 seconds, plus the normal 60 seconds stale-while-revalidate after that. This gives us the opportunity to cache queries not containing now() much longer.

Gotcha

Mutations using query parameters trigger two separate operations internally: first execution of queries to determine which documents to update, then a transaction to actually update the documents. now() will return different times for these two operations, referring to the start time of each operation.

// Give me all posts with a publish date in the future
*[_type == "post" && dateTime(now()) < dateTime(publishedAt)]

path

path(<string>) <path>

Coerces the passed string to a path, e.g. "a.b" in path("a.*").

// _id matches a.b.c.d but not a.b.c.d.e
*[_id in path("a.b.c.*")] 

// _id matches a.b.c.d and a.b.c.d.e
*[_id in path("a.b.c.**")] 

// All draft documents
*[_id in path("drafts.**")]

// Only published documents
*[!(_id in path("drafts.**"))]

references

references(<path|string|array>) <boolean>

Implicitly takes the document at the root of the current scope and recursively checks whether it contains any references to the given document ID(s). It is typically used in query filters, e.g., *[ references("abc")] will return any documents that contain a reference to the document abc. If providing the function with an array of document ids, it will return true if any of the ids are referenced. Learn more about references.

// Using the ^ operator to refer to the enclosing document. Here ^._id refers to the id
// of the enclosing person record.
*[_type=="person"]{
  name,
  "relatedMovies": *[_type=='movie' && references(^._id)]{ title }
}

round

round(<integer|float>[, <integer>]) <integer|float>

Rounds the given number to the nearest integer, or to the number of decimal places given by the second, optional argument - e.g. round(3.14) yields 3 and round(3.14, 1) yields 3.1.

select

select(<pair|any>...) <any>

Used for conditionals, i.e. "if-else" expressions. Takes a variable number of arguments that are either pairs or any other type and iterates over them. When encountering a pair whose left-hand value evaluates to true, the right-hand value is returned immediately. When encountering a non-pair argument, that argument is returned immediately. Falls back to returning null.

// If age is 18+, return "adult" string
// Else if age is 13+, return "teen" string
// Else return "child" string
select(
  age >= 18 => "adult",
  age >= 13 => "teen",
  "child"
)

// If popularity integer is more than 20, return "high" string
// Else if popularity is more than 10, return "medium" string
// Else if popularity is less than or equal to 10, return "low"
*[_type=='movie']{
  ..., 
  "popularity": select(
    popularity > 20 => "high",
    popularity > 10 => "medium",
    popularity <= 10 => "low"
)}

// You can also use select in a shorter from
// Let's say we want to conditionally join references 
// inside a Portable Text field
*[_type == "article"]{
  ...,
  body[]{
    ...,
    _type == "product" => {
      ...,
      @->{
        name,
        price
      }
    }
  }
}

string

string(<integer|float|boolean|datetime|string>) <string>

Returns the string representation of a given scalar value. Returns null when passed an invalid value, including null.

{
  "stringInteger": string(21),         // Returns "21"
  "stringFloat": string(3.14159),      // Returns "3.14159"
  "stringSciNotation": string(3.6e+5), // Returns "360000"
  "stringTrue": string(true),          // Returns "true"
  "stringFalse": string(false),        // Returns "false"
  "stringString": string("A string"),  // Returns "A string"
}

One use case for string() is to combine a string with a scalar type, which would otherwise return null.

*[0] {
  'secondsAgo': dateTime(now()) - dateTime(_createdAt),
} {
  'minutesSinceCreated': 'Created ' + string(secondsAgo / 60) + ' minutes ago.'
}

Another use case is to coerce a date field into a string in RFC3339 format, which is useful when you need to compare datetime values or perform time arithmetic operations.

// Let's imagine a document with a year field,
// which contains a four-digit number – in this example, 2009
*[0] {
  year    // Returns 2009
}

// To compare year to a datetime field, such as now(), _createdAt,
// or _updatedAt, the year field must be converted to a string in RFC3339 format,
// but trying to append a string to the year field returns null
*[0] {
  'constructedYear': year + "-01-01T00:00:00Z"    // Returns null
}

// Using the string() function, year can be coerced to a string
// and structured in RFC3339 format
*[0] {
  'constructedYear': string(year) + "-01-01T00:00:00Z"    // Returns "2009-01-01T00:00:00Z"
}

// In this way, the year field can be used to perform time arithmetic operations
*[0] {
  'secondsSinceYear': dateTime(now()) - dateTime(string(year) + "-01-01T00:00:00Z")
}

Geolocation functions

The geo namespace contains a number of useful functions for creating and querying against locations in your data. Each function must be prefixed with the geo:: namespace syntax.

Gotcha

The geo() function and functions in the geo:: namespace require v2021-03-25 or later of the GROQ API.

Geo documents

The functions in this section require a data type of geo. These geo documents are represented JSON as GeoJSON. Functions that accept a geo type will attempt to coerce non-geo-type data into the proper format following the geo() constructor rules.

geo(object)

The geo() function accepts an object as a parameter and, if possible, coerces the value to a geo-type document by a set of rules. These objects are represented in JSON as GeoJSON.

  • If the object is already a geo document, return the object.
  • If the object has a set of lat and lng (or lon) keys, return a geo object for the given point. If additional data exists on the object it will be removed from the final geo document.
  • If the object contains the key type (note: not _type), and the value of type matches one of the following strings then return a geo object with those values:
    • Point
    • LineString
    • Polygon
    • MultiPoint
    • MultiLineString
    • MultiPolygon
    • GeometryCollection
  • If none of the conditions are met, return null.

geo::latLng(latFloat, lngFloat)

The latLng function is a short-hand for creating a new geo object for a singular point. Returns a geo object from the latitude and longitude floats provided.

// Returns a geo object corresponding to the center of Oslo
geo::latLng(59.911491, 10.757933)

geo::distance(geo-point, geo-point)

The distance() function takes points and returns a numeric value for the distance between in meters.

Gotcha

The function only works between points. If lines or polygons are provided, the function will return null.

// Returns the distance in meters between Oslo and San Francisco
// 7506713.963060733
geo::distance(
  geo::latLng(59.911491, 10.757933),
  geo::latLng(37.7749, 122.4194)
)

// Returns all documents that are storefronts
// within 10 miles of the storefront geopoint
*[
  _type == 'storefront' &&
  geo::distance(geoPoint, $currentLocation) < 16093.4
]

geo::contains(geoPolygon, geo)

The contains() function returns true when the first geographic geography value fully contains the geographic geometry value. If either parameter is not a geo object – or not able to be coerced to a geo object following the rules of the geo() constructor function – the function returns null.

// Returns true if the neighborhood region is fully contained by the city region
geo::contains(cityRegion, neighborhoodRegion)

// For a given $currentLocation geopoint and deliveryZone area
// Return stores that deliver to a user's location
*[
  _type == "storefront" &&
  geo::contains(deliveryZone, $currentLocation)
]

geo::intersects(geo, geo)

The intersects() function returns true when the two areas overlap or intersect. If either parameter is not a geo object – or not able to be coerced to a geo object following the rules of the geo() constructor function the function returns null.

// Creates a "marathonRoutes" array that contains
// all marathons whose routes intersect with the current neighborhood
*[_type == "neighborhood"] {
  "marathonRoutes": *[_type == "marathon" && 
                        geo::intersects(^.neighborhoodRegion, routeLine)  
                      ]
}

Portable Text functions

The pt namespace contains functions for parsing Portable Text. Each function must be prefixed with the pt:: namespace syntax.

Gotcha

Functions in the pt:: namespace require v2021-03-25 or later of the GROQ API.

pt::text(<Portable Text array|object>) <string>

The text() function is a Sanity Content Lake GROQ filter that takes in document fields which are either a Portable Text block or an array of blocks, and returns a string in which blocks are appended with a double newline character (\n\n). Text spans within a block are appended without space or newline.

The function exists within the pt namespace and must be prefixed with pt::.

Gotcha

The text() function only works on text spans in the root children, i.e., alt text in an Image block will not be in the final plain text.

// Returns the body Portable Text data as plain text
*[_type == "post"] 
  { "plaintextBody": pt::text(body) }
  
// Scores posts by the amount of times the string "GROQ"
// appears in a Portable Text field
*[_type == "post"]
  | score(pt::text(body) match "GROQ")

Sanity functions

The sanity namespace contains functions for querying against the current environment. Each function must be prefixed with the sanity:: namespace syntax.

Gotcha

Functions in the sanity:: namespace require v2021-03-25 or later of the GROQ API.

sanity::projectId() <string>

The projectId() function returns the project ID of the current studio environment.

sanity::dataset() <string>

The dataset() function returns the dataset of the current studio environment.

{
  'projectId': sanity::projectId(),  // Returns 'hm31oq0j', for example
  'dataset': sanity::dataset()       // Returns 'production', for example
}

Query Scoring

score(scoreExp)

The score() function takes an arbitrary number of valid GROQ expressions and assigns a score to each result as a new field called _score. The _score field can be used to sort and filter items in an array.

Gotcha

score() is a pipe function and must be separated from expressions, filters, projections, and other pipe functions that precede it with the pipe operator (|).

The score is calculated depending on the expressions used. For a match expression, _score is impacted by:

  • the frequency of matches per expression ("One Fish, Two Fish, Red Fish, Blue Fish" matches "fish" more frequently than "The Rainbow Fish" matches "fish");
  • the frequency of matches overall (matching three terms in a score() function expression versus matching one term);
  • the relevance of matches, including:
    • word count ("Big Fish" – two words – matches "fish" with greater relevance than "A Fish Called Wanda" – four words – matches "fish") and
    • word length (matching a shorter term will also return a higher _score than matching a longer term – "Blue" matches "blue" with greater relevance than "Clouds" matches "clouds").

A logical OR is identical to comma-separated terms and a logical AND is identical to a match with combined terms:

// These score() functions behave identically
* | score(title match "Red" || title match "Fish")
* | score(title match "Red", title match "Fish")

// These score() functions behave identically
* | score(title match "Red" && title match "Fish")
* | score(title match "Red Fish")

For each matched boolean expression, _score is incremented.

// Each term that is true will increment _score by 1
* | score(featured, internal, _type == 'post')

// A 'post' document with featured and internal fields
// that are both true would receive a _score of 3

Gotcha

Documents that don't match the score expression(s) return a _score value of 0, but are not automatically removed from the array. The third example in the following code block provides a solution to remove results with a _score value of 0.

// Adds points to the score value depending 
// on the use of the string "GROQ" in each post's description 
// The value is then used to order the posts 
*[_type == "post"] 
  | score(description match "GROQ") 
  | order(_score desc) 
  { _score, title }
  
// Adds a point for matches in the title OR description
*[_type == "post"] 
  | score(title match "GROQ" || description match "GROQ") 
  | order(_score desc) 
  { _score, title }
  

// Orders blog posts by GROQ matches
// Then filters the results for only items that matched
// by checking for _score values greater than 0
*[_type == "post"] 
  | score(description match "GROQ") 
  | order(_score desc) 
  { _score, title }
  [ _score > 0 ]

Gotcha

score() cannot take an expression that uses dereferencing or subqueries.

boost(scoreExp, boostValue)

The boost() function can be used to create a sense of weight in a scoring algorithm. It accepts two required arguments: an expression and the amount to boost the score if the expression returns true.

Like in the score() function, a matched expression will increase _score for each instance of a match, but by a multiple of the boost value. For example, a boostValue of 3 would increase _score by three times the amount it would increment by default (that is, without boost()).

The boost() function is used inside a score() function.

Boost values greater than 1 will give that expression a greater-than-normal impact on _score. Boost values less than 1 (but greater than 0) will give that expression a lesser-than-normal impact on _score. A boost value of 0, while permitted, has the same effect as removing that expression from the score() function.

Gotcha

The boost() function accepts only constant positive integers and floats.

// Adds 1 to the score for each time $term is matched in the title field
// Adds 3 to the score if (movie > 3) is true
*[_type == "movie" && movieRating > 3] | 
  score(
    title match $term,
    boost(movieRating > 8, 3)
  )

Providing multiple boosts of different values in one score() can create robust sorting.

// Creates a scoring system where $term matching in the title
// is worth more than matching in the body
*[_type == "movie" && movieRating > 3] | score(
  boost(title match $term, 4),
  boost(body match $term, 1)
  boost(movieRating > 8, 3)
)

// Scores games by the "impressive" difference in goals
*[_type == "game"] | score(
		boost(pointDifference > 5, 5),
		boost(pointDifference > 10, 10)
	)

Boost values between 0 and 1 can be used to affect _score to a lesser extent than a default match would. This might be useful when there is a need to finely differentiate _score values that might otherwise be equal.

// Boosts _score for matches in the title OR description,
// but a match on the description now has less of an impact on _score
*[_type == "post"] 
  | score(title match "GROQ" || boost(description match "GROQ", 0.3)) 
  | order(_score desc) 
  { _score, title }

Was this article helpful?