How to implement a filter action in the Documents List Pane?
Based on the Structure Builder API documentation, you can add custom menu items to the document list pane using the menuItems() method, but there's an important limitation to be aware of.
Adding Menu Items to Document Lists
You can add custom menu items to a document list pane like this:
// structure.ts
export const structure = (S) =>
S.list()
.title('Content')
.items([
S.documentTypeListItem('post')
.child(
S.documentList()
.title('Posts')
.schemaType('post')
.filter('_type == "post"')
.menuItems([
S.menuItem()
.title('My Custom Action')
.icon(() => 'β')
.action(() => {
// Your action handler here
}),
// Add ordering menu items
S.orderingMenuItem({by: [{field: 'publishedAt', direction: 'desc'}]}),
S.orderingMenuItem({by: [{field: 'title', direction: 'asc'}]}),
])
)
])However, the key limitation is that menuItem() actions don't have a built-in way to dynamically update the parent document list's filter. The menu items can trigger custom actions, but they can't directly modify the list's filter state like you might expect.
Better Alternative: Nested List Structure
Since direct filter menu actions aren't fully supported, here's a more practical approach that significantly improves UX over basic "folder" grouping:
export const structure = (S) =>
S.list()
.title('Content')
.items([
// Create a dedicated Posts section
S.listItem()
.title('Posts')
.icon(() => 'π')
.child(
S.list()
.title('Posts')
.items([
// All posts
S.listItem()
.title('All Posts')
.icon(() => 'π')
.child(
S.documentList()
.title('All Posts')
.schemaType('post')
.filter('_type == "post"')
.defaultOrdering([{field: 'publishedAt', direction: 'desc'}])
),
// Published only
S.listItem()
.title('Published')
.icon(() => 'β')
.child(
S.documentList()
.title('Published Posts')
.schemaType('post')
.filter('_type == "post" && !(_id in path("drafts.**"))')
),
// Drafts only
S.listItem()
.title('Drafts')
.icon(() => 'π')
.child(
S.documentList()
.title('Draft Posts')
.schemaType('post')
.filter('_type == "post" && _id in path("drafts.**")')
),
S.divider(),
// Dynamic category filtering
S.listItem()
.title('By Category')
.child(
S.documentTypeList('category')
.title('Select Category')
.child(categoryId =>
S.documentList()
.title('Posts')
.schemaType('post')
.filter('_type == "post" && $categoryId in categories[]._ref')
.params({categoryId})
)
),
])
),
])This approach gives you:
- Better visual hierarchy with icons and dividers
- Quick access to common filter views as dedicated list items
- Cleaner navigation - one click to get to filtered views
- Consistent UX with how most Sanity Studios organize content
For Sorting Options
For sort actions, use the defaultOrdering() method, and Sanity will automatically add sort options to the pane menu:
S.documentList()
.title('Posts')
.filter('_type == "post"')
.defaultOrdering([{field: 'publishedAt', direction: 'desc'}])While this isn't exactly a dropdown filter in the menu bar, this nested list structure provides much better UX than basic GROQ filter folders, and it's the recommended pattern for organizing filtered views in Sanity Studio v3.
Show original thread3 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.