Creating nested product folders filtered by category in Sanity Desk Structure

5 replies
Last updated: Sep 5, 2021
I have the following schema type.

export default {
  name: "products",
  type: "document",
  title: "Products",
  fields: [
{
      name: "categories",
      type: "reference",
      title: "Categories",
      to: {
        type: "category",
      },
    },

// other file

export default {
  name: "category",
  type: "document",
  title: "Product Categories",
  fields: [
    {
      name: "title",
      type: "string",
      title: "Title",
    },
  ],
};
I was wondering if in my deskStructure if it is possible to have a products folder -> then sub category folders filtered by the the categories that are selected for that product
AI Update

Yes, absolutely! You can create a nested structure where products are organized into folders based on their categories. Here's how to do it using the Structure Builder:

// structure/index.ts
import type {StructureResolver} from 'sanity/structure'

export const structure: StructureResolver = (S) =>
  S.list()
    .id('root')
    .title('Content')
    .items([
      // Products folder with category sub-folders
      S.listItem()
        .title('Products')
        .child(
          // Fetch all categories and create a folder for each
          S.documentTypeList('category')
            .title('By Category')
            .child((categoryId) =>
              S.documentList()
                .title('Products')
                .filter('_type == "products" && references($categoryId)')
                .params({ categoryId })
            )
        ),
      
      // You can also add a flat "All Products" list
      S.listItem()
        .title('All Products')
        .child(
          S.documentTypeList('products')
        ),
      
      // Direct access to categories
      S.documentTypeListItem('category').title('Categories'),
    ])

Then wire it up in your config:

// sanity.config.ts
import {defineConfig} from 'sanity'
import {structureTool} from 'sanity/structure'
import {structure} from './structure'

export default defineConfig({
  // ...other config
  plugins: [
    structureTool({structure}),
    // ...other plugins
  ],
})

This creates a "Products" folder in your desk structure. When you click it, you'll see a list of all your categories. Clicking any category will show only the products that reference that specific category.

The key part is the GROQ filter '_type == "products" && references($categoryId)' which filters products that reference the selected category.

Note: Your schema has categories as a singular reference field. If you want to support multiple categories per product, change it to an array:

{
  name: "categories",
  type: "array",
  title: "Categories",
  of: [{
    type: "reference",
    to: {type: "category"}
  }]
}

The references() function works with both single references and arrays of references, so the same Structure Builder code will work either way!

You can learn more about customizing your desk structure in the Structure Builder guide and the Structure customization course.

Show original thread
5 replies
I have tried something like this but it just gives me a list of all the products

S.listItem()
        .title("Products by category")
        .child(
          // List out the categories
          S.documentTypeList("category")
            .title("Product by page category")
            // When a category is selected, pass its id down to the next pane
            .child((categoryId) => {
              // load a new document list
              S.documentList()
                .title("Products")
                // Use a GROQ filter to get documents.
                // This filter checks for sampleProjects that has the
                // categoryId in its array of references
                .filter('_type == "products" && $categoryId in category[]._ref')
                .params({ categoryId });ddd
            })
        ),
I am getting closer but no cigar so far. This is products this
S.listItem()
        .title("Products by category")
        .child(
          // List out the categories
          S.documentTypeList("category")
            .title("Product by page category")
            // When a category is selected, pass its id down to the next pane
            .child((categoryId) =>
              // load a new document list
              S.documentList()
                .title("Products")
                // Use a GROQ filter to get documents.
                // This filter checks for sampleProjects that has the
                // categoryId in its array of references
                .filter(
                  '_type == "products" && $categoryId in categories[]._ref'
                )
                .params({ categoryId })
            )
        ),

Here is what a query of all the products looks like, but not sure how to filter by the categoryId
I tried something like this to no avail

*[_type == "products" && "87a1892e-506b-4055-b6a5-972de7692ee5" in categories._ref]
Yay!
This ended up working


                .filter('_type == "products" && references($categoryId)')

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?