Displaying unpublished referenced documents in Sanity's desk structure

5 replies
Last updated: Oct 3, 2024
Hello everyone,
I have a question regarding the Desk-Structure and the display of documents in Sanity. I am using a
documentList
in the desk structure that is supposed to display
productModel
documents. The problem is that these documents are not displayed if they reference a product document that is not published.
Here is my current code for the `documentList`:


S.listItem()
  .title('Models')
  .icon(RocketIcon)
  .child(
    S.documentList()
      .apiVersion('v2023-11-17')
      .title('Models')
      .filter(
        '_type == "productModel" && references(*[_type=="product" && productType == "series"]._id)'
      )
  )
As soon as the
product
document is published, the corresponding
productModel
documents appear in the list. Is there a way to display the
productModel
documents in the documentList even if the referenced
product
document is not yet published?
I have already tried to extend the query to include drafts, but that doesn't seem to work.
Do any of you have an idea how I can solve this problem?

I appreciate any tips!
AI Update

The issue you're experiencing is related to how Sanity's references() function works with draft documents. When you use references() in a GROQ query, it checks the actual reference values stored in your documents. The problem is that draft documents have IDs prefixed with drafts., but references in your documents typically store the "clean" ID without this prefix.

When a product exists only as a draft (not yet published), its ID is something like drafts.product-123, but your productModel document likely stores the reference as just product-123. This mismatch causes the references() check to fail.

The recommended solution is to modify your filter to account for both the published ID and the draft ID version:

S.listItem()
  .title('Models')
  .icon(RocketIcon)
  .child(
    S.documentList()
      .apiVersion('v2023-11-17')
      .title('Models')
      .filter(
        '_type == "productModel" && references(*[_type=="product" && productType == "series" && !(_id in path("drafts.**"))]._id) || references(*[_type=="product" && productType == "series" && _id in path("drafts.**")]._id)'
      )
  )

However, there's a simpler approach that often works better: modify your inner query to explicitly include both the published and draft IDs by using string replacement:

S.listItem()
  .title('Models')
  .icon(RocketIcon)
  .child(
    S.documentList()
      .apiVersion('v2023-11-17')
      .title('Models')
      .filter(
        '_type == "productModel" && (
          references(*[_type=="product" && productType == "series"]._id) ||
          references(*[_type=="product" && productType == "series"].{"draftId": "drafts." + _id}.draftId)
        )'
      )
  )

This approach checks if your productModel references either:

  1. The published product ID
  2. The draft product ID (constructed by adding the drafts. prefix)

Why this happens: Perspectives in Sanity control which document versions you see. By default, queries return documents based on the published perspective, but when working in the Studio, you're often dealing with a mix of draft and published content. The references() function performs a literal ID match, so it needs to check against the actual IDs that exist in the Content Lake.

This solution ensures your productModel documents appear in the list regardless of whether the referenced product is published or still in draft state, which is typically the desired behavior when working in the Studio editing environment.

I would assume its the
._id
in the end of the filter that is the problem here, what happens if you remove that?
Also what happens if you change
documentList
to
documentTypeList
?
A reference field in a document will always contain the published
_id
of said document (unless you’re using weak references). A draft of a document is just a copy of the published version with the
_id
prefixed with
drafts.
. This is responsible for the mismatch in behavior you’re getting.
I also didn’t think that projections inside of filters/the
references
function worked! This is how I would approach it:
import {map} from 'rxjs'
export const structure = (S, context) =>
  S.list()
    .title('Content')
    .items([
      S.listItem()
        .title('Models')
        .child(() =>
          context.documentStore
            .listenQuery(
              `*[_type == 'movie' && productType == 'series']{
                  'screening': *[_type == 'screening' && references(string::split(^._id, '.')[1]) || references(^._id)],
              }.screening[]._id`,
            )
            .pipe(
              map((ids) => {
                return S.documentList().title('Models').filter(`_id in $ids`).params({ids})
              }),
            ),
        ),
    ])
Note that in this example I’m using the Movie DB database that you can bootstrap via the CLI.
Also I currently have a wicked cold, so hopefully this made sense 😅
thanks that works 😍
i just had to adjust the condition because otherwise documents of other types would have ended up in the list.


.listenQuery(
  `*[_type == 'movie' && productType == 'series']{'screening': *[_type == 'screening' && references(string::split(^._id, '.')[1]) || _type == 'screening' && references(^._id)],
  }.screening[]._id`,
)
get well soon
Excellent!

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?