Can you purchase custom roles on plans lower than Enterprise?

16 replies
Last updated: Jan 14, 2022
Is it possible to purchase just custom roles if you're on a lower plan than Enterprise? We want to cordon off types to certain editors. (I recognize that's part of the reason it's there as it establishes huge value but it's not even on the 1000/mo plan, so apologies for the naivete...just looking for some implementation at the modest level of other tools to hide things from specific people).
I am trying to convince someone to switch to the light side (here) and if that's a potential dealbreaker I need to know if there's a way to make an exception to persuade them. It seems like it's the only potential point of contention.
AI Update

I understand your situation – you're trying to make the case for Sanity but need to control which document types different editors can access, and you've hit the custom roles limitation. Unfortunately, custom roles cannot be purchased as an add-on for lower-tier plans. They're exclusively an Enterprise plan feature and aren't available on the Free or Growth ($1,000/mo) plans.

However, there are some workarounds you can use to achieve similar results without custom roles:

1. Structure Builder Customization

You can use the Structure tool configuration to hide document types from certain users in the Studio UI. While this doesn't enforce actual permissions (users could technically still access hidden types via API), it provides a practical UI-level separation:

// In your desk structure
import {getCurrentUser} from 'sanity'

export const structure = (S, context) => {
  const user = context.currentUser
  
  return S.list()
    .title('Content')
    .items([
      // Show certain types only to specific users/roles
      user?.roles.some(role => role.name === 'administrator')
        ? S.documentTypeListItem('sensitiveType')
        : null,
      S.documentTypeListItem('publicType'),
      // ... other items
    ].filter(Boolean))
}

2. Document-Level Permissions (Growth Plan)

The Growth plan does include document-level permissions through its 5 pre-defined roles (Administrator, Editor, Viewer, etc.). While you can't create custom roles, you might be able to work within these 5 roles and use conditional fields or validation rules to restrict editing.

3. Multiple Projects/Datasets

For a more strict separation, you could use separate projects or datasets (Growth plan includes 2 datasets) and only invite specific users to each one.

The Enterprise Conversation

If document-type-level access control is truly a dealbreaker, it's worth having a conversation with Sanity's sales team about Enterprise pricing. They offer custom pricing and may be able to work with your budget, especially if you're bringing over a team. Enterprise also includes other valuable features like SAML SSO, content resources (GROQ-filtered permissions), and dedicated support.

The Structure Builder approach above works well for many teams as a "soft" boundary – it keeps the UI clean and prevents accidental editing, even if it's not a hard security boundary. If you need actual permission enforcement at the document type level, Enterprise is currently the only path.

Show original thread
16 replies
You can't buy custom roles as an add on, unfortunately. However, you can return a different desk structure based off of the current user. There's a very brief example of this here .
Note, this method doesn't have any sort of security model in it. A document could be hidden from the Structure, but someone could still access it via the API or search and publish edits.
That's actually the simple necessity I had. They all work for the same company and all the information is protected. I think they just wanna keep happy accidents from happening or clean up the appearance so someone doesn't get distracted seeing something they'd rather not touch.
Heck they'd be happy if it happened with CSS but I don't see user info in the markup (I shouldn't haha just saying)

That code seems to suggest we could use other information about the user beyond just the role. I will have to log the object and see if anything else is exposed. If it is, is that permissible or would it break a rule/TOS? Like pinpointing IDs if they're there?
That's actually the simple necessity I had. They all work for the same company and all the information is protected. I think they just wanna keep happy accidents from happening or clean up the appearance so someone doesn't get distracted seeing something they'd rather not touch.
Heck they'd be happy if it happened with CSS but I don't see user info in the markup (I shouldn't haha just saying)

That code seems to suggest we could use other information about the user beyond just the role. I will have to log the object and see if anything else is exposed. If it is, is that permissible or would it break a rule/TOS? Like pinpointing IDs if they're there?
Thank you for the quick response by the way
That's actually the simple necessity I had. They all work for the same company and all the information is protected. I think they just wanna keep happy accidents from happening or clean up the appearance so someone doesn't get distracted seeing something they'd rather not touch.
Heck they'd be happy if it happened with CSS but I don't see user info in the markup (I shouldn't haha just saying)

That code seems to suggest we could use other information about the user beyond just the role. I will have to log the object and see if anything else is exposed. If it is, is that permissible or would it break a rule/TOS? Like pinpointing IDs if they're there?
It's permissible!
Hooray for permissible things! And for the upper and lowercase Sanity you provide
That works awesome! The deprecation notice makes me nervous: https://www.sanity.io/help/studio-user-store-currentuser-deprecated so I am running experiments to see if there's a "subscribe" way to do the same iterating/map approach.
When you're working with all this powerful stuff your mind is going "fetch and use all teh things!" but I am still adjusting to what I have available to me from the get, the async/await and coming in and out of callable functions and returns (basically my developing React skills). Fingers crossed but this is an amazing start ( if the originals are two years old and the magic goes away tomorrow I don't want something like "controlling what people can see" to be a thing that disappears. I know it's a deliberate conscious move and we could stay a while but I also like being proactive and getting ahead of stuff.
In the interest of sharing, this is an abridged version of my non-deprecated solution with some tasteful, modest name swaps.
Note the changed syntax; it's no longer an arrow function and it has braces.

I set up a variable called items at the highest local scope, and then I use the result returned from the newer userStore call to have a resolved string to check against.

The simplest, leanest way I have arrived at is, I pack the "items" variable with an array based on that condition based on how I know it'll get used eventually, and then use it just that way in the final call that asks for an array to build out the first pane.

Another way of explaining it from the other end is, I know I need to return some complete document structure at the end that has a sack of items in it, like all document structures, but which bundle of items gets thrown in that sack depends on the results of that that first condition.


export default function () {

    let items;

    userStore.me.subscribe(e => {
        
        if ("Vincent Florio" != e.name) {
            return items = [
                S.listItem()
                    .title('First item on primary Vincent list')
                    .icon(GrGallery)
                    .child(
                        S.document()
                            .title('Vincent Slider')
                            .schemaType('sliderSettings')
                            .documentId('sliderSettings')
                    ),
            ]
        } else {
            return items = [
                S.listItem()
                    .title('First item on list for everyone else')
                    .icon(GrGallery)
                    .child(
                        S.document()
                            .title('Slider for non-Vincents')
                            .schemaType('sliderSettings')
                            .documentId('sliderSettings')
                    ),
            ]
        }
    })
    return S.list()
        
        .title('Sum of All Content')
        .items(

            items



        )
}
In the interest of sharing, this is an abridged version of my non-deprecated solution.
export default function () {

    let items;

    userStore.me.subscribe(e => {
        
        if ("Vincent Florio" != e.name) {
            return items = [
                S.listItem()
                    .title('First item on primary Vincent list')
                    .icon(GrGallery)
                    .child(
                        S.document()
                            .title('Vincent Slider')
                            .schemaType('sliderSettings')
                            .documentId('sliderSettings')
                    ),
            ]
        } else {
            return items = [
                S.listItem()
                    .title('First item on list for everyone else')
                    .icon(GrGallery)
                    .child(
                        S.document()
                            .title('Slider for non-Vincents')
                            .schemaType('sliderSettings')
                            .documentId('sliderSettings')
                    ),
            ]
        }
    })
    return S.list()
        
        .title('Sum of All Content')
        .items(

            items



        )
}
Thanks so much for sharing
user S
!
Aaaand because Sanity is awesome, and it's all connected, no new options page necessary -- I already have a list of people, so an administrator can go to their page (used for a handful of other things, selectively) and just add document types from a list to exclude that person.
Haha, it's been so fun watching you learn about Sanity!
I come from an art background and have developed over time to be a creative-problem-solving programmer of sorts, so the thoughtful design is hyping me up because it calms down all the stuff that nerves me about other platforms, while rising to the challenge of all the relatively ambitious things I want to do with it -- stably, at that. Who wouldn't love a tool that matches their ambition and follows through with every weird idea you get? People like to imagine and make stuff. It's like a playground for nerds =P
I come from an art background and have developed over time to be a creative-problem-solving programmer of sorts, so the thoughtful design is hyping me up because it calms down all the stuff that nerves me about other platforms, while rising to the challenge of all the relatively ambitious things I want to do with it -- stably, at that. Who wouldn't love a tool that matches their ambition and follows through with every weird idea you get? People like to imagine and make stuff. It's like a playground for nerds =P
I'm from an architecture/design background and remember feeling exactly the same way when I first started using Sanity.

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?