Content Release API Cheat Sheet
Query and modify Content Releases using the API
Interfacing with Content Releases is similar to interfacing with other documents and relationships in the Content Lake.
Prerequisites:
- The examples below use a variety of APIs to cover common patterns. As releases aren't public, make sure your requests are authenticated and match the correct URL format for each API. For example, using Sanity clients or other integrations, you'll need to configure them with an appropriate token and permissions to view unpublished content.
- Release APIs and features are available in API version
2025-02-19
and later unless otherwise noted.
Access releases by querying for documents with a _type
of system.release
. This is the preferred method for retrieving a list of releases.
Use the client's fetch method to query for releases.
Input
import { createClient } from "@sanity/client";
const client = createClient({
projectId: '<project-id>',
dataset: '<dataset>',
useCdn: true,
apiVersion: 'vX',
token: '<token>',
perspective: 'raw' //default
})
const query = "*[_type == 'system.release']"
const params = {}
client.fetch(query, params).then((data)=>{
console.log(data)
})
Output
[
{
"_createdAt": "2024-11-26T21:30:57Z",
"finalDocumentStates": null,
"_updatedAt": "2024-12-17T16:33:26Z",
"_type": "system.release",
"name": "rHw6FBu82",
"_id": "_.releases.rHw6FBu82",
"state": "active",
"metadata": {
"releaseType": "scheduled",
"title": "End of year release",
"intendedPublishAt": "Mon Dec 30 2024"
},
"publishAt": "2024-12-30T08:00:00Z",
"_rev": "JmI5JuFTDPq3paS6p09Jmu",
"userId": "paATypsg4"
},
{
"publishAt": null,
"_rev": "1kbjGQwz5Z0FmijO2l7Lwl",
"finalDocumentStates": [
{
"id": "versions.rglJO3Sfg.movie_70981",
"_key": "1kbjGQwz5Z0FmijO2l7M0E"
}
],
"_id": "_.releases.rglJO3Sfg",
"state": "published",
"metadata": {
"title": "Quick fixes",
"releaseType": "asap"
},
"_createdAt": "2024-11-26T22:01:56Z",
"_type": "system.release",
"name": "rglJO3Sfg",
"_updatedAt": "2024-12-02T17:32:59Z",
"userId": ""
},
{
"_createdAt": "2024-11-26T18:34:14Z",
"name": "rqZSzJ1uS",
"finalDocumentStates": [
{
"id": "versions.rqZSzJ1uS.movie_10681"
}
],
"userId": "paATypsg4",
"_id": "_.releases.rqZSzJ1uS",
"state": "published",
"_updatedAt": "2024-12-05T17:22:00Z",
"metadata": {
"releaseType": "scheduled",
"description": "Experimental updates for testing",
"title": "Experimental updates",
"intendedPublishAt": "2024-12-05T17:22:00.000Z"
},
"publishAt": "2024-12-05T17:22:00Z",
"_rev": "5dKCVUpSDccCmU1E23aUAc",
"_type": "system.release"
},
// ...
],
You access releases by querying for documents with a _type
of system.release
using the Query API. This is the preferred method for retrieving a list of releases.
Use the following GROQ query in the API request:
// Target the document type
*[_type == 'system.release']
// This is also equivalent
releases::all()
Example GET request
curl "https://<project-id>.api.sanity.io/vX/data/query/<dataset-name>?query=<GROQ-QUERY>" \ --H "Authorization: Bearer <token>" \
Example response
{
"query": "*[_type == 'system.release']",
"result": [
{
"_createdAt": "2024-11-26T21:30:57Z",
"finalDocumentStates": null,
"_updatedAt": "2024-12-17T16:33:26Z",
"_type": "system.release",
"name": "rHw6FBu82",
"_id": "_.releases.rHw6FBu82",
"state": "active",
"metadata": {
"releaseType": "scheduled",
"title": "End of year release",
"intendedPublishAt": "Mon Dec 30 2024"
},
"publishAt": "2024-12-30T08:00:00Z",
"_rev": "JmI5JuFTDPq3paS6p09Jmu",
"userId": "paATypsg4"
},
{
"publishAt": null,
"_rev": "1kbjGQwz5Z0FmijO2l7Lwl",
"finalDocumentStates": [
{
"id": "versions.rglJO3Sfg.movie_70981",
"_key": "1kbjGQwz5Z0FmijO2l7M0E"
}
],
"_id": "_.releases.rglJO3Sfg",
"state": "published",
"metadata": {
"title": "Quick fixes",
"releaseType": "asap"
},
"_createdAt": "2024-11-26T22:01:56Z",
"_type": "system.release",
"name": "rglJO3Sfg",
"_updatedAt": "2024-12-02T17:32:59Z",
"userId": ""
},
{
"_createdAt": "2024-11-26T18:34:14Z",
"name": "rqZSzJ1uS",
"finalDocumentStates": [
{
"id": "versions.rqZSzJ1uS.movie_10681"
}
],
"userId": "paATypsg4",
"_id": "_.releases.rqZSzJ1uS",
"state": "published",
"_updatedAt": "2024-12-05T17:22:00Z",
"metadata": {
"releaseType": "scheduled",
"description": "Experimental updates for testing",
"title": "Experimental updates",
"intendedPublishAt": "2024-12-05T17:22:00.000Z"
},
"publishAt": "2024-12-05T17:22:00Z",
"_rev": "5dKCVUpSDccCmU1E23aUAc",
"_type": "system.release"
},
],
"syncTags": [
"s1:r6H+EQ"
],
"ms": 3
}
To view only active releases, and exclude archived releases, adjust your GROQ query to compare the state
property.
*[_type == 'system.release' && state == 'active']
// or, with the function
releases::all()[state == 'active']
As releases are Sanity documents, you can interact with them the same way you would any other document. The preferred method is with the Actions API.
Create the action:
// Example release edit action
{
"actionType": "sanity.action.release.edit",
// Exclude the _.releases. prefix from the releaseId
"releaseId":"rEGM2JqQ3",
"patch": {
"set": {
"metadata": {
"title": "New release title"
}
}
}
}
Include the action in the <action>
portion below:
Example request
curl -X POST 'https://<project-id>.api.sanity.io/vX/data/actions/<dataset-name>' \ -H 'Authorization: Bearer <token>' \ -H 'Content-Type: application/json' \ -D '{"actions":[<action>]}'
Example response
{
"transactionId": "71GXGRT7Io3CmtCwpl6n8V"
}
You can also edit releases using the Mutate API.
Query all documents associated with a release.
The sanity::partOfRelease GROQ function accepts a release ID and returns all documents associated with the release.
Use the function in a GROQ query and pass the release ID string.
import { createClient } from "@sanity/client";
const client = createClient({
projectId: '<project-id>',
dataset: '<dataset>',
useCdn: false,
apiVersion: '2025-02-19',
token: '<token>',
perspective: 'raw'
})
const query = "*[sanity::partOfRelease(<releaseID>)] { _id }"
const params = {}
client.fetch(query, params).then((data)=>{
console.log(data)
})
Query all versions (published, drafts, and release versions) of a document.
The sanity::versionOf
GROQ function accepts a document ID and returns all versions of a document.
Use the function in a GROQ query and pass the document ID string.
import { createClient } from "@sanity/client";
const client = createClient({
projectId: '<project-id>',
dataset: '<dataset>',
useCdn: false,
apiVersion: '2025-02-19',
token: '<token>',
perspective: 'raw'
})
const query = "*[sanity::versionOf(<documentID>)] { _id }"
const params = {}
client.fetch(query, params).then((data)=>{
console.log(data)
})
Gotcha
The function expects a non-prefixed document ID. For example: abc123
is acceptable, but drafts.abc123
and versions.r1324.abc123
are not.
The function also works with the Query API and anywhere that supports GROQ functions.
Use the document ID, along with the Doc API endpoint to retrieve all versions of a specific document. The includeAllVersions
boolean query parameter returns all versions for the document.
Example request for ID movie_70981
GET /vX/data/doc/production/movie_70981?includeAllVersions=true
Example response
{
"documents": [
{
"_createdAt": "2018-06-13T08:57:45Z",
"_id": "movie_70981",
"_rev": "1kbjGQwz5Z0FmijO2l7Lwl",
"_type": "movie",
"_updatedAt": "2024-12-02T17:32:59Z",
// ...
},
{
"_createdAt": "2018-06-13T08:57:45Z",
"_id": "drafts.movie_70981",
"_rev": "3276f9d1-0343-4b78-a79e-8c6561942f1b",
"_type": "movie",
"_updatedAt": "2024-12-02T17:14:27Z",
// ...
},
{
"_createdAt": "2018-06-13T08:57:45Z",
"_id": "versions.rHw6FBu82.movie_70981",
"_rev": "a000fa99-072c-434c-8669-825103a111b7",
"_type": "movie",
"_updatedAt": "2024-11-27T18:36:19Z",
// ...
}
],
"omitted": []
}
Content Releases use a layering system that layers document versions atop one another, allowing you to create a custom perspective stack. You can learn more about layering in the Content Releases User Guide.
Perspective in addition to accepting the raw
, published
, and drafts
states, also accepts a comma-separated list of release names. Releases take priority from left to right.
For example, in the perspective a,b,c
you would see changes in a
take priority over b
and c
, and changes in b
take priority over c
.
The published
perspective is automatically added to the end, so even if a release only contains changes to one document, the response will include all matching published documents in addition to the release changes.
To query against a list of releases, use the perspective
query parameter and order the release names by priority from left to right. For example: ?perspective=a,b,c
.
Using a GROQ query such as *[_type == 'movie'] { _id }
will return all document IDs that match the releases layer.
Query all movie documents from the listed release perspectives
GET https://<projectId>.api.sanity.io/vX/data/query/<dataset>?query‌‌‌‌‌‌=<GROQ-QUERY>&perspective=<release-name1>,<release-name2>
Example response
{
"query": "*[_type == 'movie']{ _id }",
"result": [
{ "_id": "a306e7cf-ea18-4a43-8ce2-0586073c41c8" },
{ "_id": "movie_10681" },
{ "_id": "movie_118340" },
{ "_id": "movie_126889" },
{ "_id": "movie_157336" },
{ "_id": "movie_17654" },
],
"syncTags": ["s1:+jIWIw"],
"ms": 5
}
In addition to the raw
, published
, and drafts
values, the client also accepts an array of release name strings. You can add drafts
to the end of the array to include drafts that aren't part of any release.
Edit the perspective value to insert your release names.
import { createClient } from "@sanity/client";
const client = createClient({
projectId: '<project-id>',
dataset: '<dataset>',
useCdn: false, // Don't use the CDN for draft/release previewing
apiVersion: '2025-02-19',
token: '<token>',
perspective: ['<release-name-1>', '<release-name-2>']
})
const query = "*[_type == 'movie']"
const params = {}
client.fetch(query, params).then((data)=>{
console.log(data)
})
Another common pattern is to extend your client configuration for releases and draft preview by creating a new client from the existing one.
import { createClient } from "@sanity/client";
const client = createClient({
projectId: '<project-id>',
dataset: '<dataset>',
useCdn: true,
apiVersion: '2024-08-01',
token: '<token>',
perspective: 'published' //default
})
const previewClient = client.withConfig({
useCdn: false,
apiVersion: '2025-02-19',
perspective: ['<release name>', 'drafts']
})
The Release documents can be queried and trigger assigned webhooks.
The most useful way of triggering webhooks might be off the release state
.
A release may have the following states:
active
: The general state of a release that is not within one of the other states.scheduled
: A state resulting from calling thesanity.action.release.schedule
action on the release.published
: A state resulting from either calling thesanity.action.release.publish
action, or when a scheduled release is published due to reaching itspublishAt
time.archived
: A state resulting from calling thesanity.action.release.archive
action.deleted
: A state resulting from calling thesanity.action.release.delete
action on an archived release.
Additional transient states exist to indicate the asynchronous points when releases move between states:
scheduling
/unscheduling
: Intermediate states which will exist when moving to/from thescheduled
state.archiving
/unarchiving
: Intermediate states which will exist when moving to/from thearchived
state.publishing
: Intermediate state which will exist before reaching thepublished
state. Note that a scheduled release will also transition throughpublishing
.
To create a webhook that listens to all new releases, define a webhook rule as follows:
"rule":{
"on":["create"],
"filter":"_type == 'system.release'"
}
Additionally filters can enable triggering only on releases of a particular state. In this example only releases that have transitioned into a published
state will trigger the webhook:
"rule": {
"on": ["update"],
"filter": "_type == 'system.release' && delta::changedAny(state) && state == 'published'"
}