Functions
Custom GROQ functions
v2021-03-25
All GROQ APIs above v1 now support custom defined GROQ functions.
// Query
fn ex::info($person) = $person{name, age};
*[_type == "person"] { "info": ex::info(@) }
This allows you to create modular, reusable parts of queries.
- Get started with custom functions.
- View the reference documentation.
- See the updated GROQ language spec.
Content Releases APIs and new perspective defaults
v2025-02-19
This release introduces new features and changes to support Content Releases. For more details on Content Releases, see the Studio release notes.
Breaking changes
The following items may break behavior in your applications. See the details below for further explanations and migration paths.
- The default perspective has changed to only display published content.
- Raw perspective requests now include
versions.
prefixed documents in addition todrafts.
and published documents. - A bug in GROQ queries that caused empty keys to expand has been fixed. If you are relying on this behavior, see the details below.
versions.**
documents are not returned in API versions prior to 2025-02-19
. You must opt-in to this version to query versions-prefixed documents.
Highlights
Perspective changes
The default perspective has changed from raw
to published
. This includes:
/query
now usesperspective=published
by default.- GraphQL now uses
perspective=published
by default.
To return to the previous behavior, set perspective
to raw
.
New query perspectives
In addition to the existing named perspective values, you can also pass one or more release IDs to the query endpoint's perspective parameter. For example: perspective=release-a,release-b,release-c
. This will give you a view on your dataset as if release-c
had been published, followed by release-b
and finally release-a
. This is generalisation of the existing previewDrafts
functionality to support Content Releases.
Learn more in the Content Releases API documentation.
New includeAllVersions
parameter
The following endpoints now include a boolean includeAllVersions
parameter. Set it to true and use an authenticated request to include all versions.
New path prefix for versions
Prior to 2025-02-19
, queries on /query and similar APIs returned published documents and drafts.**
documents when using the raw
or previewDrafts
perspectives. In 2025-02-19 and later, a versions.**
path is introduced for Content Release document versions.
Gotcha
versions.**
documents are not returned in API versions prior to 2025-02-19
. You must opt-in to this version to query versions-prefixed documents.
If you rely on path prefixes alone, you'll want to use perspectives instead to control which documents are returned by queries.
Additional Actions
The Actions API now includes actions for managing Content Releases. Along with this, actions such as sanity.action.document.discard
and sanity.action.document.replaceDraft
have been deprecated in favor of sanity.action.document.version.discard
and sanity.action.document.version.replace
.
See the Actions API reference for more details.
New GROQ functions
sanity::versionOf(documentID)
: Checks for versions of a provided document ID. Use inside a filter to return matching documents. For example:*[sanity::versionOf("abc123")]
.sanity::partOfRelease(releaseID)
: Checks whether a document is part of a given release. For example, fetch all the version documents in releaserel-summer
using*[sanity::partOfReleases("rel-summer")
.releases::all()
: Returns all releases.
Learn more about these in the GROQ functions reference documentation.
New lastRevision
parameter
The document history API now accepts a lastRevision
parameter, which returns the current data for an existing document, or the last state before deletion for a deleted document.
Fix for empty key projection bug
This version also introduces a fix to a GROQ bug whereby empty keys in projections would expand into the object.
For example:
Input
{
"": {
"a": "b"
}
}
response
{
"a": "b"
}
This is corrected in 2025-02-19, and instead the query will return the following:
{
"": {
"a": "b"
}
}
To replicate the older behavior in the new API version, update the empty string with the ellipsis operator (...
). For example:
{
... {
"a": "b"
}
}
Stabilization of the array::intersects() function for GROQ
v2021-10-21
The array::intersects()
function for GROQ, previously available in experimental releases, is now fully stable and compatible with all dated API versions. You can now use this function in webhook filters, permission filters, and with groq-js, allowing for more flexible array operations across different contexts.
New GROQ functions and versioning
v2021-03-25
This release targeting v2021-03-25
and v2021-10-21
of the Content Lake brings several new function namespaces to GROQ, as well as introduces the first formalized version of the GROQ language specification.
New GROQ functions
There are three new namespaces for functions added to the specification, which have been implemented across all GROQ tooling. These are:
array::
functions: Perform array operations on lists, such as removing all null values, building text strings from a list of names, or generating a list of all unique document _types.
array::compact(<array>)
- removes allnull
values from an arrayarray::join(<array>, <token>)
- concatenates all array elements into one string, separated by a specified token.array::unique(<array>)
- removes duplicate values from an array (this works for values that can be compared for equality, specifically numbers, strings, booleans, andnull
, and will not work for values that are arrays or objects)
math::
functions: Run common mathematical operators on numeric values. For example, you can add the prices across multiple products or return the maximum discount available within a cart of products.
math::avg(<array-of-numbers>)
- calculates the average value (arithmetic mean) of an array of numbers.math::max(<array-of-numbers>)
- returns the largest numeric value of an array of numbers.math::min(<array-of-numbers>)
- returns the smallest numeric value of an array of numbers.math::sum(<array-of-numbers>)
- calculates the sum of an array of numbers.
string::
functions: Manipulate text or validate that information matches a given prefix. For example, get a list of the articles that start with “How to” or split a comma-separated string of author names into an array.
string::split(<string>, <delimiter-token>)
- turns a string into an array of substrings based on a delimiting token.string::startsWith(<string>, <string-pattern>)
- checks if a prefix string exactly matches the start of another string.
To learn all about the new GROQ functions, read the developer update accompanying this release, and visit our documentation.
GROQ1.revision1
With this release, we are also pleased to announce the formal language specification of GROQ, as well as a new versioning scheme. Taking inspiration from other well-known language specs, like HTML and SQL, we settled on the following format:
GROQ-<major version #>.revision<#>
The current version of the specification is GROQ-1.revision1
. This version does not include any breaking changes. To learn more about the history and future of the GROQ language, read the blog post from Co-founder and CTO Simen Svale Skogsrud: Content is Queryable: (Re)Introducing GROQ.
Bug fixes in GROQ implementation
v2021-03-25
This release fixes smaller bugs for GROQ v2021-03-25.
- Fixes a bug where dereferencing inside sliced arrays (e.g.
*[_type == "book"]{"authors": authors[0...2]{era->}}
) would return incorrect result. - Fixes a bug where scoping was incorrect while dereferencing arrays:
*[_type == "book"]{"authors": authors[]->{..., "bookName": ^.name}}
select
now produces a query error when given invalid arguments. Examples of invalid arguments areselect(title, title2)
(multiple fallback values) andselect(title, isGood => title2)
(alternatives after the fallback)
GROQ specification compatibility, introduction of namespaces, and new functions
v2021-03-25
Content Lake is a full refactor of Sanity's backend and query engine. You can start using it by upgrading and specifying an API version for your client. Content Lake follows the GROQ specification and introduces both bug fixes and new features.
We have updated the documentation to reflect the newest version. This changelog contains the breaking changes and migration paths you need to take. You can take your time and stay on v1
for as long as you need, while you test and compare your queries against the new version. You can also specify the API version for only new queries to gradually move over. Let us know in the community if you have questions or need help.
Breaking changes from v1
to v2021-03-25
Correct parent operator behavior (^
)
The GROQ ^ operator now works correctly in all scopes.
This fixes the known issue where the ^
operator only worked in subqueries. In all other scopes, it returned the root of the current scope, instead of the parent scope.
*[_type=="person"]{
name,
someObj{
name,
// Old Behavior: "parentName" returns someObj.name
// New Behavior: "parentName" returns root name value
"parentName": ^.name
}
}
Consistent handling of true
/false
/null
for equality/comparisons operators
GROQ now uses three-valued logic consistently:
>
,>=
,<
,<=
returnsnull
when the operands are of different types.&&
,||
handlesnull
"as expected":null && true
⇒null
andnull || true
⇒true
.
Note that ==
has changed slightly:
- It now always return either
true
orfalse
(nevernull
). - You can compare against
null
and get the expected result:123 == null
⇒false
andnull == null
⇒true
. - Comparisons between other types than strings/booleans/numbers always return
false
.
This also means that in
works with null
: null in foo
will return true
if foo
is [null, 1, 2]
.
in null
always returns null
.
Consistent array traversal
We have cleaned up the behavior for array traversal. For example, queries that contained multiple array traversals that don't work in v1
, now work as expected in v2021-03-25
and onward.
*["link" in body[].markDefs[]._type]
// v1: No results
// v2021-03-25: An array of documents that has a link annotation in their "body" field
// Data:
[
{
"_type": "book",
"authors": [
{ "names": ["MH", "Holm"] },
{ "names": ["Bob"] },
]
}
]
// Query:
*[_type == "book"].authors[].names
// v1:
[null]
// v2021-03-25:
[
[
"MH",
"Holm",
],
[
"Bob"
]
]
// You can also add an additional `[]` to flatten it completely:
// Query:
*[_type == "book"].authors[].names[]
// v2021-03-25:
[
"MH",
"Holm",
"Bob"
]
Null values are not removed in projections
If you have stored null
values in your documents, these are no longer removed in projections.
Override attributes while spreading objects and arrays (...
)
You can now override attributes while using the spread operator (...
).
// Data
[{"title": "A", "customTitle": "B", "_type": "post"}]
// Query:
*[_type == "post"]{..., "title": customTitle}
// Output from v1:
[{"title": "A"}]
// Output from v2021-03-25:
[{"title": "B"}]
All numbers are float64
All numbers are now 64-bit floats. Previously we used 64-bit integer representation in certain contexts.
String ordering is more consistent
Previously ordering by strings would in some context use a "smart" numeric ordering instead of proper string comparison:
// Query:
["foo4", "foo12"]|order(@)
// v1:
["foo4", "foo12"]
// v2021-03-25:
["foo12", "foo4"]
// However, *|order(foo) has always (both before and after) used proper string comparison.
Deprecated: The is
prefix operator
The is
operator has been deprecated. You can use the equivalent comparison instead:
// v1:
*[_type is "post"]
// v2021-03-25:
*[_type == "post"]
Deprecated: Function calls without parentheses
Previously you could call functions without parentheses: *[length "foo" > 3]
. This is now no longer possible and is a syntax error. You should use *[length(foo) > 3]
instead.
Deprecated: $now
and $identity
You can use now()
and identity()
instead.
Projections only work on objects, and returns null otherwise
Whenever you apply the projection operator ({}
) on a non-object then it returns null
instead of executing the object expression.
This means that the following query *[foo == bar][0]{a}
now correctly returns null
if there were no results. Previously this returned {}
.
Negative indexes are respected in slicing
Previously negative indexes were not respected: [_type == "article"][-1]
was equivalent to [_type == "article"][1]
. This has now been fixed and -1
returns the last document.
Creating an empty attribute is now an error
The following query now fails: *[_type == "bar" && @[""] == "bar"]
since empty attribute keys are not allowed.
order() only works on arrays, and returns null otherwise
Passing | order(…)
on non-arrays return a null value instead of converting it to an array.
defined()
defined(x)
is now only false when x
is null
. Previously it was false for empty arrays and objects as well.
Pipe operator works in fewer situations
The pipe operator (|
) used to work between nearly all access operators, filters, and slices (e.g., * | [filter]
, * | (foo)
, * | [2..4]
, and * | [2]
were all permitted). The pipe operator remains required before the score()
and order()
pipe functions, and is optional before a projection, but using one before a filter or traversal syntax will throw an error.
New features for GROQ
Namespaces
Namespaces allow for a stronger grouping of functionality within the GROQ specification. They create dedicated spaces for global functions, as well as safer distinctions for specific implementations of GROQ. Learn more.
New functions: score()
and boost()
The score()
function computes a _score
for each document from how well the expression matches the document. The documents will also be sorted by score (from high to low) if no other order()
function is specified. Note that all documents will be scored (even those that don't match it at all) so you typically want to add a limit. Read more about scoring and boosting.
Portable Text to plain-text: pt::text()
The pt::text()
function takes 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. Read more about getting plain text from Portable Text.
New functions for geospatial queries
The geo
namespace contains a number of useful functions for creating and querying against locations in your data. Query for distance, intersections, and more.
Performance improvements
Ordering and filtering on date times
It's now possible to filter and order efficiently on date times:
// Filter
*[_type == "post" && dateTime(publishedAt) > dateTime("2021-01-01T12:00:00")]
// Order
*[_type == "post"]|order(dateTime(publishedAt) desc)
Projections on huge documents
If you have huge documents, but your queries have projections that filter away most of the data (e.g., *[_type == "post"]{title, description}
) you may see performance improvements.
Expressions with mixed &&
and ||
Expressions that use both &&
and ||
will now often be much faster (depending on how complicated they are).
*[
_type == "post" &&
slug == "hello" &&
("news" in tags || products[0].name == "Sanity")
]
// v1: Slow!
// v2021-03-25: Much faster!