Structuring Nested Categories and Tags in Sanity Desk

5 replies
Last updated: Jan 27, 2026
So I struggle with structuring my documents. I have tags that are categorised. I want to be able to create a category and within that category I want to create tags in sanity.
this is how far I got. I tried to use filter in deskStructure. But then I wasn't able to create more categories.

I am not sure what should be my approach here. Any ideas or tips?
😅
AI Update

I totally get the frustration - this is a really common pattern people need! The key issue you're running into is that filtered lists in Structure Builder are read-only views. You can't create new documents directly in them because Sanity doesn't know what filter values to pre-populate.

Here's the best approach that gives you both organized browsing AND the ability to create documents:

The Solution: Separate Document Types with References

Create two document types where tags reference their parent category:

// schemas/category.ts
export default {
  name: 'category',
  type: 'document',
  title: 'Category',
  fields: [
    {
      name: 'title',
      type: 'string',
      title: 'Title',
      validation: (Rule) => Rule.required()
    },
    {
      name: 'slug',
      type: 'slug',
      title: 'Slug',
      options: { source: 'title' }
    }
  ]
}

// schemas/tag.ts
export default {
  name: 'tag',
  type: 'document',
  title: 'Tag',
  fields: [
    {
      name: 'title',
      type: 'string',
      title: 'Title',
      validation: (Rule) => Rule.required()
    },
    {
      name: 'category',
      type: 'reference',
      title: 'Category',
      to: [{type: 'category'}],
      validation: (Rule) => Rule.required()
    }
  ]
}

Structure Builder Setup

Now create a Structure Builder configuration that gives you both the hierarchical view and flat creation lists:

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

export const structure: StructureResolver = (S) =>
  S.list()
    .title('Content')
    .items([
      // Hierarchical browsing view
      S.listItem()
        .title('Tags by Category')
        .child(
          S.documentTypeList('category')
            .title('Categories')
            .child((categoryId) =>
              S.documentList()
                .title('Tags')
                .filter('_type == "tag" && category._ref == $categoryId')
                .params({ categoryId })
            )
        ),
      
      S.divider(),
      
      // Flat lists for creating new documents
      S.documentTypeListItem('category').title('All Categories'),
      S.documentTypeListItem('tag').title('All Tags'),
    ])

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
  ],
  // ...schemas
})

How to Use This Setup

To create a new category:

  1. Go to "All Categories" in the sidebar
  2. Click the "+" button
  3. Fill in the category details

To create a new tag:

  1. Go to "All Tags" in the sidebar
  2. Click the "+" button
  3. Fill in the tag title and select its category from the reference field

To browse tags by category:

  1. Go to "Tags by Category"
  2. Click on a category
  3. See all tags filtered to that category

Why This Works

The "Tags by Category" view uses child resolvers to create a hierarchical browsing experience - perfect for viewing and editing existing content in an organized way. The "All Categories" and "All Tags" lists give you unfiltered access where Sanity can properly handle document creation without needing to guess filter values.

This pattern is exactly how parent-child taxonomies are meant to work in Sanity. You get the best of both worlds: intuitive organization for browsing/editing and straightforward workflows for creating new content! 😊

Show original thread
5 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?