How to hide dashboard, menu items, and schema pages based on user role or email?

4 replies
Last updated: Jul 22, 2021
Btw, can I hide some dashboard, menu items, schema pages from users with a “editor” role or based on a e-mail, username?
AI Update

Yes, you can hide dashboard items, menu items, and schema pages from users based on their role or email! This is a powerful feature for customizing the Studio experience based on user permissions.

Key Approaches

1. Custom Roles with Content Resources

First, you'll want to set up custom roles (available on Enterprise plans) to control what content users can access. Custom roles use GROQ filters to define which documents users can see and edit.

2. Structure Builder Customization

You can customize what appears in the Studio using the Structure Builder API based on user context. The Structure Builder receives a context object that includes currentUser information:

import type {StructureResolver} from 'sanity/structure'

export const structure: StructureResolver = (S, context) => {
  const user = context?.currentUser
  const roles = user?.roles.map((r) => r.name)
  
  // Check if user has specific role
  const isEditor = roles?.includes('editor')
  
  return S.list()
    .id('root')
    .title('Content')
    .items([
      // Always show articles
      S.documentTypeListItem('article').title('Articles'),
      
      // Only show settings for non-editors
      ...(!isEditor ? [
        S.documentTypeListItem('settings').title('Settings')
      ] : [])
    ])
}

3. Accessing User Information

The currentUser object provides:

  • email: User's email address
  • id: User ID
  • name: User's name
  • roles: Array of role objects with name and title

You can also use the userHasRole() helper function:

import {userHasRole} from 'sanity'

if (userHasRole(currentUser, 'editor')) {
  // Hide certain items
}

4. Filtering by Email or Username

You can filter based on email directly:

const isSpecificUser = user?.email === 'editor@example.com'

5. New Document Options

You can also control which document types appear in the "Create" menu:

// In sanity.config.ts
export default defineConfig({
  document: {
    newDocumentOptions: (prev, {currentUser}) => {
      const roles = currentUser?.roles.map(r => r.name)
      
      // Remove certain types for editors
      if (roles?.includes('editor')) {
        return prev.filter(item => 
          !['settings', 'config'].includes(item.templateId)
        )
      }
      
      return prev
    }
  }
})

6. Conditional Plugin Configuration

You can even hide entire tools/plugins based on role:

plugins: [
  media(),
  {
    name: 'conditional-tools',
    tools: (prev, {currentUser}) =>
      userHasRole(currentUser, 'editor')
        ? prev.filter((tool) => tool.name !== 'media')
        : prev
  }
]

Important Notes

  • Roles are additive: If a user has multiple roles, they get the combined permissions
  • Security: UI hiding doesn't replace proper content permissions - always set up custom roles for actual access control
  • Dataset privacy: For restricted content, set datasets to private (public datasets bypass role restrictions)

Check out the Studio customizations lesson for detailed examples and the custom roles documentation for setting up proper permissions.

Show original thread
4 replies
Ahhh just found it let’s see. But he’s using groupNames ‘wizards’ where do you create such groups?
Haha, I think those are just examples. I mean, if you have an enterprise plan you could create a ‘wizards’ role if you were so inclined.
lol wizards Cool so I only need to check if editors else show admin roles.
So basically I just need to move my current fields inside checks right


import S from '@sanity/desk-tool/structure-builder'

import {
   FiPaperclip,
   FiUser,
   FiUsers,
   FiEdit,
   FiSettings,
   FiMessageSquare,
   FiHeart,
   FiInfo,
   FiNavigation,
   FiFileText,
   FiFile,
   FiTag,
   FiCheckSquare
} from 'react-icons/fi'

import PagePreview from '../studio/src/components/preview/page/PagePreview'

const hiddenDocTypes = (listItem) =>
   ![
      'author',
      'post',
      'page',
      'category',
      'theme',
      'comment',
      'friends',
      'route',
      'navigation',
      'general',
      'featured',
      'cookie'
   ].includes(listItem.getId())

const siteMenu = () =>
   S.list()
      .title('Website')
      .items([
         S.listItem()
            .title('Posts')
            .child(S.documentTypeList('post').title('Posts overview'))
            .schemaType('post')
            .icon(FiEdit),

         S.divider(),

         S.listItem()
            .title('Pages')
            .child(S.documentTypeList('page').title('Pages overview'))
            .schemaType('page')
            .icon(FiFile),

         S.divider(),

         S.listItem()
            .title('Authors')
            .child(S.documentTypeList('author').title('Authors overview'))
            .schemaType('author')
            .icon(FiUser),

         S.divider(),

         S.listItem()
            .title('Themes')
            .child(S.documentTypeList('theme').title('Themes overview'))
            .schemaType('theme')
            .icon(FiHeart),

         S.divider(),

         S.listItem()
            .title('Categories')
            .child(S.documentTypeList('category').title('Categories overview'))
            .schemaType('category')
            .icon(FiTag),

         S.divider(),

         S.listItem()
            .title('Comments')
            .child(S.documentTypeList('comment').title('Comments overview'))
            .schemaType('comment')
            .icon(FiMessageSquare),

         S.divider(),

         S.listItem()
            .title('Routes')
            .child(S.documentTypeList('route').title('Routes'))
            .schemaType('route')
            .icon(FiPaperclip),

         S.divider(),

         // Settings menu with sub-menu's.
         S.listItem()
            .title('Settings')
            .child(
               S.list()
                  // Sets a title for our new list
                  .title('Settings')
                  // Add items to the array
                  // Each will pull one of our new singletons
                  .items([
                     S.listItem()
                        .title('General')
                        .icon(FiInfo)
                        .child(
                           S.document()
                              .schemaType('general')
                              .documentId('generalSettings')
                              .title('General Settings')
                        ),

                     S.divider(),

                     S.listItem()
                        .title('Navigation')
                        .icon(FiNavigation)
                        .child(
                           S.document()
                              .schemaType('navigation')
                              .documentId('navigationSettings')
                              .title('Navigation Settings')
                        ),

                     S.divider(),

                     S.listItem()
                        .title('Featured articles')
                        .icon(FiFileText)
                        .child(
                           S.document()
                              .schemaType('featured')
                              .documentId('featuredArticlesSettings')
                              .title('Featured Articles Settings')
                        ),

                     S.divider(),

                     S.listItem()
                        .title('Cookie Consent')
                        .icon(FiCheckSquare)
                        .child(
                           S.document()
                              .schemaType('cookie')
                              .documentId('cookieSettings')
                              .title('Cookie Consent Settings')
                        ),

                     S.divider(),

                     S.listItem()
                        .title('Friends team')
                        .icon(FiUsers)
                        .child(
                           S.document()
                              .schemaType('friends')
                              .documentId('settingsFriends')
                              .title('Friends Settings')
                        )
                  ])
            )
            .icon(FiSettings),

         // `S.documentTypeListItems()` returns an array of all the document types
         // defined in schema.js. We filter out those that we have
         // defined the structure above.
         ...S.documentTypeListItems().filter(hiddenDocTypes)
      ])

export default siteMenu

export const getDefaultDocumentNode = (props) => {
   /**
    * Here you can define fallback views for document types without
    * a structure definition for the document node. If you want different
    * fallbacks for different types, or document values (e.g. if there is a slug present)
    * you can set up that logic in here too.
    * <https://www.sanity.io/docs/structure-builder-reference#getdefaultdocumentnode-97e44ce262c9>
    */
   const { schemaType } = props
   if (schemaType === 'page') {
      return S.document().views([
         S.view.form(),
         S.view.component(PagePreview).title('Preview Page')
      ])
   }

   return S.document().views([S.view.form()])
}

Exactly.

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?