Unlock seamless workflows and faster delivery with our latest releases - Join the deep dive

Querying draft and published versions of documents in Sanity

18 replies
Last updated: Mar 30, 2020
Hey all, I have an issue with a GROQ query.
There are 2 schemas:
blogPost
and
category
. Each document of type
blogPost
references to a
category
.Now, if I edit a category, a new copy of the published document is created with id
drafts.[id_of_published_doc]

I want to be able to display all the
blogPost
documents for a draft category — what is the best way to query all
blogPost
that reference a category, even when the current category is a draft and not the published document?
Before I found out about this "draft" issue, the query was relying on matching the
_ref
with the
_id


*[_type == "category"] {
	name,
	"allBlogPosts": *[_type == "blogPost" && category._ref == ^._id ] {
		title,
	}
}
So far I tried to rely of draft and published versions to have the same
slug
, but the query doesn't seem to work:
*[_type == "category"] {
	name,
	"allBlogPosts": *[_type == "blogPost" && category->slug.current == ^.slug.current ] {
		title,
	}
}

Mar 23, 2020, 9:03 AM
Hi
user S
, did you find a solution for this? I’m having the same problem.
Mar 27, 2020, 6:53 PM
No, I haven't. I was hoping someone from the Sanity team could help!
Mar 27, 2020, 10:05 PM
Yes that would be helpful! Maybe
user M
can point us in the right direction?
Mar 27, 2020, 10:09 PM
Hi both! Just to make sure I’m understanding the issue correctly, could you explain why you might need the draft version of the
category
document for the
blogPost
reference?
I just checked but it seems like the reference to the
category
maintains the original, non-draft
_id
and the original
category
document with the non-draft
_id
still exists too (as you said, it’s a copy), so it continues to function correctly. Also, even though a
category
is in draft status, it will still add the non-draft
_id
to the
_ref
field when adding the category as a reference.
Finally, when querying for all categories through
*[_type == "category"]{_id}
, it gives both the non-draft and the draft version. You can filter out the draft versions though:
*[_type == 'category' && !(_id in path("drafts.**"))]
What am I missing to help you guys?
🙂
Mar 29, 2020, 3:07 PM
P.S. If you want to query both the draft and the non-draft
_id
in the query above, you could try something like this:
*[_type == "category"] {
	name,
	"allBlogPosts": *[_type == "blogPost" && ^._id match category._ref] {
		title,
	}
}
Mar 29, 2020, 3:19 PM
Thanks
user M
! I’m not sure about Marco’s case, but in my situation I have an artist document and artwork objects. Artwork objects have a reference to an artist.
On an artist-page I query some data from the artist document like so
*[_type == 'artist' && slug.current == $slug][0]
, but also all artworks that have a reference to the artist. like
*[_type == "artwork" && references(^._id)]
. Now when I want to preview a draft of an artist-page like
*[_type == 'artist' && slug.current == $slug && _id in path('drafts.**')][0]
the query for the artworks refers to the draft
_id
and subsequently no artworks show up they are connected to the to the public version of the artist, and not the draft version.
So what I think I’m looking for is a way to have
^._id
but remove the
drafts.
-prefix with some kind of string manipulation. Hope that’s clear!
Mar 29, 2020, 9:03 PM
Hi Bauke, thanks for explaining! I think I’m with you now. I’m not sure what your artist reference field in
artwork
is called, but let’s say it’s called
artist
for this example. Then could you try replacing the artwork query by something like this?
*[_type == "artwork" && ^.id match artist._ref]
Mar 29, 2020, 9:57 PM
Thanks, I tried your solution, but I’m afraid it didn’t work. The artist reference is indeed called
artist
but I think the problem is the same: the
^.id
in that solution still points to the draft-id while the artwork expects the non-draft-id.
If it helps, here is the complete original query and the query with your solution:


*[_type == 'artist' && slug.current == $slug && _id in path('drafts.**')][0] {
  _id,
  someOtherStuff,
  "artworks": *[_type == "artwork"&& references(^._id)] {
    _id,
    someOtherStuff,
  }
}

*[_type == 'artist' && slug.current == $slug && _id in path('drafts.**')][0] {
  _id,
  someOtherStuff,
  "artworks": *[_type == "artwork" && ^.id match artist._ref] {
    _id,
    someOtherStuff,
  }
}
If I’m not mistaken, the
^.id
in the above queries refer to the same.
Mar 30, 2020, 7:37 AM
Indeed, they do refer to the same draft
_id
but
match
should let you find a match between the draft and non-draft ids, or so I thought anyway 😉 Could you try it the other way around just for fun?
... && artist._ref match ^.id
If it doesn’t work, I’ll have a closer look at this.
Mar 30, 2020, 7:56 AM
Alright, I’ll give that a try!
Mar 30, 2020, 7:58 AM
Both ways round give an Internal server error.
Mar 30, 2020, 8:01 AM
Hmm, that shouldn’t happen though. Do you have any repo by any chance that you could share a link to in DM? I’m happy to try and get this working for you.
Mar 30, 2020, 8:02 AM
I think my use case is quite similar. On non-production links, for each document I always pick the draft version (if there is one) over the published document.
In my case, each
blogPost
document holds a reference to
category
document (e.g. this blog post is in the "Travel" category).
In the website, there is a page for each category showing all of the blog posts that belong to that category.

My issue comes up when the category is the draft (and not the published version). How can I select all of the
blogPost
documents that referenced the published version of the current category draft?
Hope the explanation is clear, thank you!
Mar 30, 2020, 8:53 AM
user M
I tried the query that you suggested:

*[_type == "category"] {
	name,
	"allBlogPosts": *[_type == "blogPost" && ^._id match category._ref] {
		title,
	}
}
and I get an Internal Server Error too
Mar 30, 2020, 9:08 AM
Hi both, I was just able to reproduce the Internal Server Error that you’re both getting. Seems like the query works when you replace
^._id
with an actual draft id string but not as an enclosed record reference. I’ll share that with the team because it seems strange.
There are workarounds though, like the one Marco already kind of suggested in the original post — but with the $slug variable that Bauke has in his implementation as it won’t work with
^.slug.current
for some reason:

*[_type == 'artist' && slug.current == $slug][0] {
  _id,
  someOtherStuff,
  "artworks": *[_type == "artwork" && artist->slug.current == $slug] {
    _id,
    someOtherStuff
  }
}
Now let’s figure out a way to fix this for Marco too without needing a separate query for the slug
😉
Mar 30, 2020, 6:06 PM
Did some more testing. I think this is a more solid solution to the issue:
*[_type == 'artist' && slug.current == $slug] | order(_updatedAt desc)[0] {
  _id,
  someOtherStuff,
  "artworks": *[_type == "artwork" && (artist._ref == ^._id || 'drafts.' + artist._ref == ^._id)] {
    _id,
    someOtherStuff
  }
}
The
order
is specified to ensure it picks the most recently updated artist document for the current slug. Afterwards it checks for the
artist._ref
with and without a
'drafts.'
string in front of it to find a match. I couldn’t find an easy way to slice
drafts.
off the
^._id
so I added it to the
_ref
instead. Can’t beat ’em, join ‘em 🤷‍♂️
This should work in Marco’s case as well:

*[_type == "category"] {
	name,
	"allBlogPosts": *[_type == "blogPost" && (category._ref == ^._id || 'drafts.' + category._ref == ^._id)] {
		title,
	}
}
Mar 30, 2020, 6:33 PM
Hey Peter, I just tried the query that you suggested and it worked! I didn't know I could do string concatenation like that in a GROQ query.
Thank you so much again for looking into this — hopefully we helped finding some edge case-related bug that will improve Sanity overall!
Mar 30, 2020, 7:33 PM
To be honest, I didn’t know either until I tried today 😄 I think you might not need the parentheses and even
_type == "blogPost"
in the
allBlogPosts
query but it doesn’t harm to keep them.
Thanks both for reporting this and insisting to have it looked at! We won’t find all these edge cases without you
Mar 30, 2020, 7:40 PM

Sanity– build remarkable experiences at scale

Sanity is a modern headless CMS that treats content as data to power your digital business. Free to get started, and pay-as-you-go on all plans.

Was this answer helpful?