APIs and SDKs

Common patterns for the Content Releases API

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 use several APIs. Releases aren't public, so requests must be authenticated with a token that has permission to read unpublished content, and must use the correct URL format for each API.
  • Release APIs and features are available in API version 2025-02-19 and later unless otherwise noted.
  • Queries that read release information need a perspective that can see it. Set the perspective to raw, or to a stack of release names. See Perspectives for Content Lake.
  • The @sanity/client examples require version 7.8.0 or later, which is when createVersion gained baseId. See Content Releases and versions with @sanity/client.

Examples that don't construct a client assume the configured client shown above.

Create a new release

Releases are Sanity documents of type system.release, so you can mutate them like any other document. The Sanity client also offers helper methods:

import {createClient} from '@sanity/client'

const client = createClient({
  projectId: 'YOUR_PROJECT_ID',
  dataset: 'production',
  apiVersion: '2025-02-19',
  token: process.env.SANITY_API_TOKEN,
  useCdn: false, // Release and draft content is never served from the CDN cache
})

const {releaseId} = await client.releases.create({
  metadata: {
    title: 'New bike release',
    releaseType: 'asap',
  },
})

For the full list of metadata properties, see the type definitions in your editor or Create a new release in the Actions API guide.

Create a document version in a release

Use client.createVersion() to add a version of an existing published document to a release. Prefer baseId so Sanity copies the current published content for you. See Content Releases and versions with @sanity/client for the full guide.

Modify release information

To change a release's metadata, pass a patch along with the releaseId:

import {createClient} from '@sanity/client'

const client = createClient({
  projectId: 'YOUR_PROJECT_ID',
  dataset: 'production',
  apiVersion: '2025-02-19',
  token: process.env.SANITY_API_TOKEN,
  useCdn: false, // Release and draft content is never served from the CDN cache
})

const release = await client.releases.edit({
  releaseId: 'RELEASE_NAME',
  patch: {
    set: {
      metadata: {
        releaseType: 'asap',
      },
    },
  },
})

You can also edit releases using the Mutate API.

Get all releases for a project and dataset

Query release documents with the releases::all() GROQ function. This is the preferred way to list releases.

JS client

Use the client's fetch method to query for releases.

Input

import {createClient} from '@sanity/client'

const client = createClient({
  projectId: 'YOUR_PROJECT_ID',
  dataset: 'production',
  apiVersion: '2025-02-19',
  token: process.env.SANITY_API_TOKEN,
  useCdn: false, // Release and draft content is never served from the CDN cache
  perspective: 'raw',
})

const releases = await client.fetch('releases::all()')
console.log(releases)

Response

[
    {
      "_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": "2024-12-30T08:00:00Z"
      },
      "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"
    }
  // ...
  ]

Query API

Use the same releases::all() function through the Query API.

Use the following GROQ query in the API request:

releases::all()

Input

Example response

{
  "query": "releases::all()",
  "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": "2024-12-30T08:00:00Z"
      },
      "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.

releases::all()[state == 'active']

Get all documents from a release

Query all documents associated with a release.

JS client

import {createClient} from '@sanity/client'

const client = createClient({
  projectId: 'YOUR_PROJECT_ID',
  dataset: 'production',
  apiVersion: '2025-02-19',
  token: process.env.SANITY_API_TOKEN,
  useCdn: false, // Release and draft content is never served from the CDN cache
})

const {result: documents} = await client.releases.fetchDocuments({
  releaseId: 'RELEASE_NAME',
})

console.log(documents)

fetchDocuments returns the release's document versions. To filter or project them in a query instead, use the sanity::partOfRelease GROQ function.

sanity::partOfRelease GROQ function

The sanity::partOfRelease GROQ function accepts a release name and returns every document in that release.

Release ID vs. release name

Use the function in a GROQ query and pass the release name string.

import {createClient} from '@sanity/client'

const client = createClient({
  projectId: 'YOUR_PROJECT_ID',
  dataset: 'production',
  apiVersion: '2025-02-19',
  token: process.env.SANITY_API_TOKEN,
  useCdn: false, // Release and draft content is never served from the CDN cache
  perspective: 'raw',
})

const query = "*[sanity::partOfRelease($releaseName)]{ _id }"
const params = {releaseName: 'RELEASE_NAME'}

const documents = await client.fetch(query, params)
console.log(documents)

Get all versions of a document

Query all versions (published, drafts, and release versions) of a document.

sanity::versionOf GROQ function

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'
import {getPublishedId} from 'sanity'

const client = createClient({
  projectId: 'YOUR_PROJECT_ID',
  dataset: 'production',
  apiVersion: '2025-02-19',
  token: process.env.SANITY_API_TOKEN,
  useCdn: false, // Release and draft content is never served from the CDN cache
  perspective: 'raw',
})

const query = "*[sanity::versionOf($publishedId)]{ _id }"
const documentId = 'versions.rABC123.movie_70981'
const params = {publishedId: getPublishedId(documentId)}

const versions = await client.fetch(query, params)
console.log(versions)

Gotcha

The function also works with the Query API and anywhere that supports GROQ functions.

Doc API

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.

Input

GET /v2025-02-19/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": []
}

Use releases in perspective queries

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.

Besides raw, published, and drafts, the perspective parameter 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.

Query API

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.

A GROQ query such as *[_type == 'movie']{ _id } returns the document IDs that match the release layer.

Input

GET https://YOUR_PROJECT_ID.api.sanity.io/v2025-02-19/data/query/production?query=<GROQ_QUERY>&perspective=RELEASE_NAME_1,RELEASE_NAME_2

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
}

JavaScript client

The client also accepts an array of release names. Priority runs left to right, so put drafts first when unpublished edits should win over release content, and last when release content should win. published is appended automatically; drafts is not.

Edit the perspective value to insert your release names.

import {createClient} from '@sanity/client'

const client = createClient({
  projectId: 'YOUR_PROJECT_ID',
  dataset: 'production',
  apiVersion: '2025-02-19',
  token: process.env.SANITY_API_TOKEN,
  useCdn: false, // Release and draft content is never served from the CDN cache
  perspective: ['drafts', 'RELEASE_NAME_1', 'RELEASE_NAME_2'],
})

const movies = await client.fetch("*[_type == 'movie']")
console.log(movies)

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: 'YOUR_PROJECT_ID',
  dataset: 'production',
  apiVersion: '2025-02-19',
  useCdn: true, // Safe here: this client only reads published content
  perspective: 'published', // default
})

const previewClient = client.withConfig({
  token: process.env.SANITY_API_TOKEN,
  useCdn: false, // Release and draft content is never served from the CDN cache
  perspective: ['drafts', 'RELEASE_NAME'],
})

Trigger webhooks or functions by release state

Release documents are queryable, so they can trigger webhooks.

The release state is usually the most useful trigger:

A release reports its status on the state property. For the full list of states, including the transient states a release passes through, and the transitions between them, see Content release document flow.

Functions

You can create Sanity Functions that activate when you create a release, or when it moves through different states.

Webhooks

To trigger on every new release, use this filter in the rule object of a GROQ-powered webhook:

{
  "rule": {
    "on": ["create"],
    "filter": "_type == 'system.release'"
  }
}

To trigger only on releases entering a given state, compare state in the filter. This rule fires only when a release transitions into published:

{
  "rule": {
    "on": ["update"],
    "filter": "_type == 'system.release' && delta::changedAny(state) && state == 'published'"
  }
}

Find documents marked for unpublishing

A release can mark published documents to be unpublished when it publishes. Those version documents carry _system.delete. Unpublishing removes the published document; the content stays in the dataset. Replace $releaseName with your own release name.

releases::all()[name == $releaseName] {
  _id,
  "docs": *[sanity::partOfRelease(^.name) && _system.delete == true]._id
}

Patch all versions of a document

If you need to make an update to all versions of a document, you can use the mutations API along with the sanity::versionOf GROQ function.

Query and sort releases to build a perspective stack

To build a perspective stack outside a preview or visual-editing context, query, filter, and sort the releases yourself. This example filters on _type == "system.release" directly so it can group by metadata.releaseType in one query.

This example:

  • Queries releases by their type.
  • Orders results by date, descending, so the furthest in the future come first. Scheduled releases use publishAt or metadata.intendedPublishAt; other types use _createdAt.
  • Builds a perspective stack you can query with. Including every release behaves much like raw, with layering differences that depend on the order.

In practice, you should adjust the criteria for which releases to include.

Perspectives take priority from left to right. Learn more in the perspective documentation.

Get perspective stack in custom components

If you're building with Studio, you can use the usePerspective hook to retrieve the active perspective stack.

Was this page helpful?