👀 Our most exciting product launch yet 🚀 Join us May 8th for Sanity Connect

Perspectives for Content Lake

Perform the same query but with different results based on the published or draft status of a document.

Note on data privacy, drafts, and authentication

Sanity offers a range of tools for granularly managing access to your content. The rest of this article presumes that all requests are made from an authenticated client with permissions to see both drafts and published content.

You can learn more about data security, the drafts model, or how Sanity limits access to content in public datasets using IDs and paths at the following destinations.

The Perspectives feature allows you to query your datasets from a different viewpoint with minimal configuration. You can use the previewDrafts perspective to treat all drafts and in-flight changes as published, or the published perspective to exclude all unpublished changes from your results. The default raw perspective behaves the same as if no perspective is set – i.e., it will by default return drafts and published content side by side for authenticated requests.

// Example JS/TS client configuration
import {createClient} from '@sanity/client'

const client = createClient({
  ...config,
  perspective: 'published', // 'raw' | 'previewDrafts' | 'published'
})

Look at your content from a different point of view

Core to the idea of composable, structured content is the ability to weave content from any number of independent but interconnected documents into whatever shape is required on the consuming end. Sanity’s Content Lake lets you write queries that can filter, combine, merge, expand references, and apply transformations to the original content in your dataset.

This flexibility also means that often your query results will be made up of bits and pieces from a multitude of source documents and that sometimes previewing what your app, website, or experience will look like after you hit that publish button can become complicated and cumbersome. Previewing content changes before committing to production in an environment that is as realistic as possible is vital to a smooth editorial experience.

Using the previewDrafts and published perspectives

Perspectives allows your GROQ queries to run against an alternate view of the content in your dataset – very similar to how “views” work in traditional databases. The perspective is set as an additional parameter in the client config or API call so that your queries can remain identical between different implementations, such as a preview and production deployment. In addition to the default perspective, which can be explicitly set by using perspective: 'raw', two new built-in perspectives are initially available:

  • The previewDrafts perspective, in which queries return your content “as if” all draft documents (i.e., unpublished changes in Studio) were published
  • The published perspective, in which queries return your content “as if” no in-flight unpublished changes existed

Requesting either of these alternative perspectives is a matter of adding one line to your client configuration or passing a URL parameter if you’re using the HTTP API.

// Example JS/TS client configuration
import {createClient} from '@sanity/client'

const client = createClient({
  ...config,
	useCdn: false, // must be false when using 'previewDrafts'
  perspective: 'previewDrafts', // 'raw' | 'previewDrafts' | 'published'
})
// Example using HTTP API
/data/query/production?query=*[]&perspective=previewDrafts

Gotcha

Queries using the previewDrafts perspective are not cached in the CDN, and will return an error if useCdn is not set to false. You should always explicitly set useCdn to false when using previewDrafts!

Example output from different perspectives

Let’s look at a very minimal example dataset with different perspectives applied.

raw

import {createClient} from '@sanity/client'

const client = createClient({
  ...config,
  perspective: 'raw', // default value, optional 
})

const authors = await client.fetch('*[_type == "author"]')

Making our initial query with the default raw perspective (explicitly set in this example but can safely be omitted) reveals that we are looking at a dataset of authors that contains the following documents:

  • a published document (Ursula Le Guin)
    • with unpublished changes in a corresponding draft document (Ursula K. Le Guin)
  • a draft document that has never been published (Stephen King)
  • a published document with no pending changes (Terry Pratchett)
[
  {
    "_type": "author",
    "_id": "ecfef291-60f0-4609-bbfc-263d11a48c43",
    "name": "Ursula Le Guin"
  },
  {
    "_type": "author",
    "_id": "drafts.ecfef291-60f0-4609-bbfc-263d11a48c43",
    "name": "Ursula K. Le Guin"
  },
  {
    "_type": "author",
    "_id": "drafts.f4898efe-92c4-4dc0-9c8c-f7480aef17e2",
    "name": "Stephen King"
  },
  {
    "_type": "author",
    "_id": "6b3792d2-a9e8-4c79-9982-c7e89f2d1e75",
    "name": "Terry Pratchett"
  }
]

published

import {createClient} from '@sanity/client'

const client = createClient({
  ...config,
  perspective: 'published',
})

const authors = await client.fetch('*[_type == "author"]')

Running the same query with the published perspective specified yields a result where all drafted changes and unpublished documents have been excluded. This perspective is useful for ensuring that unpublished content never ends up in a production deployment.

[
  {
    "_type": "author",
    "_id": "ecfef291-60f0-4609-bbfc-263d11a48c43",
    "name": "Ursula Le Guin"
  },
  {
    "_type": "author",
    "_id": "6b3792d2-a9e8-4c79-9982-c7e89f2d1e75",
    "name": "Terry Pratchett"
  }
]

previewDrafts

import {createClient} from '@sanity/client'

const client = createClient({
  ...config,
	useCdn: false, // must be false, required for this perspective
  perspective: 'previewDrafts',
})

const authors = await client.fetch('*[_type == "author"]')

Viewed through the previewDrafts perspective, our content is returned with all drafts applied. Documents are deduped in favor of the draft version, and unpublished draft documents are returned as if published. Note also that each document now has an _originalId property which identifies its origin.

[
  {
    "_type": "author",
    "_id": "ecfef291-60f0-4609-bbfc-263d11a48c43",
    "_originalId": "drafts.ecfef291-60f0-4609-bbfc-263d11a48c43",
    "name": "Ursula K. Le Guin"
  },
  {
    "_type": "author",
    "_id": "f4898efe-92c4-4dc0-9c8c-f7480aef17e2",
    "_originalId": "drafts.f4898efe-92c4-4dc0-9c8c-f7480aef17e2",
    "name": "Stephen King"
  },
  {
    "_type": "author",
    "_id": "6b3792d2-a9e8-4c79-9982-c7e89f2d1e75",
	  "_originalId": "6b3792d2-a9e8-4c79-9982-c7e89f2d1e75",
    "name": "Terry Pratchett"
  }
]

Gotcha

The previewDrafts perspective has an upper limit of 1000 draft documents. If the number of in-progress unpublished documents exceeds 1000, queries with the previewDrafts perspective will return an error and no data. To allow the perspective to work as normal again, you must reduce the number of draft documents by publishing some of your changes.

You can use the following GROQ snippet to get the current number of drafts in your dataset: count(*[_id in path('drafts.**')])

GraphQL

Content Source Maps are supported in the Sanity GraphQL API v2023-08-01 and later. Read more about this in the GraphQL docs.

Gazing into the future

Perspectives for Content Lake is released with previewDrafts and published built-in, but the story doesn’t end here. To learn about our long-term vision for this feature, visit our release announcement blog post.

Was this article helpful?