How to Group Documents by Published Date with the Desk Tool Plugin
Grouping documents by published date in the Structure Tool is definitely achievable, though it requires a bit of creative use of the Structure Builder API. The main challenge is that the Structure Builder doesn't have a built-in "group by date" feature, so you'll need to manually create list items for your date groupings.
Here are the main approaches you can take:
Approach 1: Static Date-Based Lists
The most straightforward (and performant) approach is to create predefined date ranges that filter your documents. This works well for common use cases:
import type {StructureResolver} from 'sanity/structure'
export const structure: StructureResolver = (S) =>
S.list()
.title('Content')
.items([
S.listItem()
.title('My Readings')
.child(
S.list()
.title('Readings by Date')
.items([
S.listItem()
.title('Today')
.child(
S.documentList()
.title("Today's Readings")
.filter('_type == "reading" && publishedAt >= $today')
.params({
today: new Date().toISOString().split('T')[0]
})
),
S.listItem()
.title('Yesterday')
.child(
S.documentList()
.title("Yesterday's Readings")
.filter('_type == "reading" && publishedAt >= $yesterday && publishedAt < $today')
.params({
yesterday: new Date(Date.now() - 86400000).toISOString().split('T')[0],
today: new Date().toISOString().split('T')[0]
})
),
S.listItem()
.title('This Week')
.child(
S.documentList()
.title("This Week's Readings")
.filter('_type == "reading" && publishedAt >= $weekStart')
.params({
weekStart: new Date(Date.now() - 7 * 86400000).toISOString().split('T')[0]
})
),
// Add more date ranges as needed
])
),
])This approach is reliable, performs well, and doesn't require any async operations. The downside is that it's not truly dynamic—you're defining fixed date ranges rather than automatically creating a list item for each unique date.
Approach 2: Dynamic Date Grouping (With Caveats)
While the Structure Builder does support async functions, using them to fetch unique dates and dynamically create list items comes with important performance considerations. The Structure Builder will re-evaluate these async functions frequently, potentially causing performance issues as your document count grows.
If you still want to pursue this approach, be aware that you'll be making client queries every time the structure loads. Here's the pattern, but use it cautiously:
import type {StructureResolver} from 'sanity/structure'
export const structure: StructureResolver = (S, context) =>
S.list()
.title('Content')
.items([
S.listItem()
.title('My Readings')
.child(async () => {
const client = context.getClient({apiVersion: '2024-01-01'})
// This query will run frequently - consider performance implications
const documents = await client.fetch(`
*[_type == "reading" && defined(publishedAt)]
| order(publishedAt desc) [0...50] {
publishedAt
}
`)
// Group by date in JavaScript
const dateGroups = documents.reduce((groups: Record<string, number>, doc: any) => {
const date = doc.publishedAt.split('T')[0]
groups[date] = (groups[date] || 0) + 1
return groups
}, {})
return S.list()
.title('My Readings by Date')
.items(
Object.entries(dateGroups).map(([date, count]) =>
S.listItem()
.title(`${new Date(date).toLocaleDateString('en-GB', {
day: 'numeric',
month: 'long',
year: 'numeric'
})} (${count})`)
.id(date)
.child(
S.documentList()
.title(`Readings from ${date}`)
.filter('_type == $type && publishedAt >= $start && publishedAt < $end')
.params({
type: 'reading',
start: `${date}T00:00:00Z`,
end: `${date}T23:59:59Z`
})
)
)
)
}),
])Important limitations of this approach:
- The query runs every time the structure is evaluated, which can impact performance
- I've limited the query to 50 documents to avoid excessive processing—adjust based on your needs
- With 8-10 documents per day, this could become slow as your content grows
- Consider caching strategies if you go this route
Recommendation
For your use case with 8-10 documents per day, I'd recommend starting with Approach 1 (static date ranges) using categories like "Today", "Yesterday", "This Week", "This Month", etc. This provides excellent performance and covers most editorial workflows.
If you absolutely need to see every unique date, consider implementing a custom component using the Structure Builder's component integration that can handle the date grouping logic with better control over caching and performance.
The Structure Builder documentation has more examples of filtered lists, and the pattern of using S.documentList() with .filter() and .params() is the foundation for any date-based filtering approach.
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.