How to get previous and next items of same type with GROQ?

14 replies
Last updated: Jan 28, 2026
How can I retrieve with GROQ the previous and next item from the same type, such as a news post?
{
    "post": *[_type == "news" && slug.current == "${page.params.slug}"] [0],
    "prev": "???",
    "next": "???",
}
AI Update

You can retrieve previous and next items in GROQ by using comparison operators with ordering. Here's how to structure your query:

{
  "post": *[_type == "news" && slug.current == $slug][0],
  "prev": *[_type == "news" && _createdAt < *[_type == "news" && slug.current == $slug][0]._createdAt] | order(_createdAt desc)[0],
  "next": *[_type == "news" && _createdAt > *[_type == "news" && slug.current == $slug][0]._createdAt] | order(_createdAt asc)[0]
}

However, this approach queries the current post multiple times, which isn't efficient. A better pattern is to use a projection to avoid redundant queries:

*[_type == "news" && slug.current == $slug][0] {
  ...,
  "prev": *[_type == "news" && _createdAt < ^._createdAt] | order(_createdAt desc)[0],
  "next": *[_type == "news" && _createdAt > ^._createdAt] | order(_createdAt asc)[0]
}

The ^ operator references the parent scope (the current post), so you can compare against its _createdAt field without re-querying.

Key points:

  • Use < to get items before the current one, then order(_createdAt desc)[0] to get the most recent previous item
  • Use > to get items after the current one, then order(_createdAt asc)[0] to get the next item
  • Replace _createdAt with whatever field you want to use for ordering (like publishedAt, orderRank, etc.)
  • The ^ operator helps you reference the parent document's fields within projections

If you're ordering by a custom field like publishedAt or want to handle published vs draft documents, adjust the filter accordingly:

*[_type == "news" && slug.current == $slug && !(_id in path("drafts.**"))][0] {
  ...,
  "prev": *[_type == "news" && !(_id in path("drafts.**")) && publishedAt < ^.publishedAt] | order(publishedAt desc)[0],
  "next": *[_type == "news" && !(_id in path("drafts.**")) && publishedAt > ^.publishedAt] | order(publishedAt asc)[0]
}

This pattern works well for pagination-style navigation through content of the same type. Just make sure the field you're ordering by is indexed properly for better query performance.

Show original thread
14 replies

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?