An opinionated guide to Sanity Studio
Sanity Studio is an incredibly flexible tool with near limitless customisation. Here's how I use it.
Go to An opinionated guide to Sanity StudioThe promise of an excellent Sanity Studio is that Developers craft uniquely useful experiences for Authors.
This guide covers how to customise some of the most commonly used parts of the Studio on a per-user or per-role basis.
An Administrator in your Studio likely needs visibility across all of your content types. But an Author that is only concerned with a specific domain could be given a simpler experience with less options.
With Desk Structure configured in your Studio, we can subscribe to the userStore in order to find out details about the current user.
The example below will display different items to a user whose roles include one with the name "administrator".
// ./sanity.config.ts
import {visionTool} from '@sanity/vision'
import {defineConfig} from 'sanity'
import {deskTool, StructureBuilder} from 'sanity/desk'
import {schemaTypes} from './src/schemas'
const authorItems = (S: StructureBuilder) => [
S.documentTypeListItem('post').title('Posts'),
S.documentTypeListItem('author').title('Authors'),
]
const adminItems = (S: StructureBuilder) => [
S.documentTypeListItem('project').title('Projects'),
S.documentTypeListItem('setting').title('Settings'),
]
export default defineConfig({
// ...other config options
plugins: [
// ...other plugins
deskTool({
structure: (S, {currentUser}) => {
const isAdmin = currentUser?.roles.find((role) => role.name === 'administrator')
const title = isAdmin ? 'Administrator Content' : 'Content'
const items = isAdmin ? [...authorItems(S), S.divider(), ...adminItems(S)] : authorItems(S)
return S.list().title(title).items(items)
},
}),
],
})In addition to customizing which Desk Structure items are shown, we can customize what items are shown in a Document List based on GROQ's identity() function.
The GROQ function identity() returns the ID of the User performing the query. Super-useful in the Studio!
For example, perhaps instead of listing all author documents, you only want the Author to see and edit their own document.
S.listItem()
.id('profile')
.title('My Profile')
.child(() =>
S.documentList()
.title('My Profile')
.schemaType('author')
.filter('_type == "author" && userId == identity()')
),However, this will only work if your author documents contain a correct value in a userId field, let's fix that!
Hiding documents in Desk Structure will not remove them from search results in the Studio or in Reference fields. Apply additional filtering to those where possible.
Adding Sanity User ID's to documents can make them easier to query with GROQ using identity()
Using Initial Value Templates you can create individual templates to populate new documents with static or dynamic values.
Using a similar function to the one above, we can create a template for post type documents to pre-fill an author field with the User ID.
// ./sanity.config.ts
export default defineConfig({
// ... all other config
schema: {
// ... schema types, etc
templates: (prev, {currentUser}) => {
// User is not guaranteed to be logged in
if (!currentUser?.id) {
return prev
}
// Create new template for new author type documents
const authorWithUserId = {
id: `author-with-user-id`,
title: `Author with User ID`,
schemaType: `author`,
value: {
userId: currentUser.id,
},
}
// Remove the default template for author type documents
const prevFiltered = prev.filter((template) => template.id !== `author`)
return [...prevFiltered, authorWithUserId]
},
}
})author field readOnly so the data is not corruptedIt is possible, and likely, your schema will include something like an author type document. Or a greater abstraction; a person type document that can be assigned the value of "author" either as a string or reference.
These are different from Sanity Users – also known as Project Members – and that's fine! An author document likely contains much more details about that person than their Sanity User object does – like biography, contact and social details.
So instead of adding a User ID to every article, page, etc. Add it to a single author document which is connected to those types by a reference.
This way the User ID is only assigned to a single document, and a query like this will resolve all of their connected documents based on who is querying them:
*[_type in ["article", "page"] && author->userId == identity()]An exception to this is you need to lock an entire document at the Permissions level based on a User ID, as Content Resources cannot resolve references. See the section on Permissions below.
While we have hidden items of the Desk Structure from specific Users, they will still be able to create new documents of all types from the Navbar (next to the search box).
In the section above, we customized Initial Value Templates to remove the default new author document template. Here we can ensure globally that non-administrators cannot create new project or setting type documents.
// ./sanity.config.ts
export default defineConfig({
// ... all other config
document: {
// ... all other document config
newDocumentOptions: (prev, {currentUser}) => {
// User is not guaranteed to be logged in
if (!currentUser?.id) {
return prev
}
const isAdmin = currentUser?.roles.find((role) => role.name === 'administrator')
// Administrators can create any document type
if (isAdmin) {
return prev
}
// Remove the option to create new 'project' or 'setting' type documents
const prevFiltered = prev.filter(
(template) => !['project', 'setting'].includes(template.templateId)
)
return prevFiltered
},
},
})
Fortunately the hidden and readOnly properties in schema have direct access to a currentUser variable.
Here we're hiding a price field to any user without the administrator role.
defineField({
name: 'price',
type: 'number',
// Hide the price field from non-administrators
hidden: ({currentUser}) => !currentUser?.roles.find((role) => role.name === 'administrator'),
})readOnly has the same currentUser property, and it can even be added at the document level to set all fields to readOnly.
In this example, we'll lock the document to any user that is not an administrator or editor.
export default defineType({
name: 'project',
title: 'Project',
type: 'document',
readOnly: ({currentUser}) => !currentUser?.roles.find((role) => role.name === 'administrator'),
fields: [ // ...all your fields ]
})Setting a field hidden will not remove its value from the Inspect panel, so Users will still be able to see the value of the field.
Making fields readOnly will only protect the field in the Studio, a sufficiently motivated User could still write to the field with an API call.
The only true way to secure these fields and documents is with access control.
Sanity's Roles and Permissions API will ensure that built-in Document Actions like Publish or Delete cannot be clicked by Users that lack those permissions.
But for your custom Actions, you'll need to inspect the current user to hide or disable the Action selectively.
Fortunately, the Studio provides a useCurrentUser hook, which can be used inside a Document Action as they are rendered inside of React scope.
Here's a code example that disables the action for non-administrators.
import {DocumentActionProps, useCurrentUser} from 'sanity'
export default function CreateMilestone(props: DocumentActionProps) {
const user = useCurrentUser()
const isAdmin = user ? user.roles.find((role) => role.name === 'administrator') : false
return {
// Disable the button for non-"administrator" users
disabled: !isAdmin,
label: 'Create Milestone',
onHandle: async () => {
// ...your function logic
props.onComplete()
},
}
}You could also:
null to hide the action from non-administrators orsanity.config.ts in the document.actions configurationOn Enterprise plans, access to view and edit documents in the Studio experience can be automatically be customized thanks to Content Resources.
Going back to our userId the example earlier, it's possible to write a rule that would only give access to users editing an author document other than the one with their userId with the following rule:
_type == "article" && userId == identity()Because Content Resources cannot dereference fields, this userId would also need to be present on any document type you wish to restrict access to in the same way.
The same logic can filter document lists, search and reference fields at the permission level. By restricting view access to documents that do not have your userId.
All Published documents are viewable in a Public Dataset. If you're trying to restrict the ability to View documents using Permissions, you must also switch to a Private Dataset.
Configuring every part of the Studio into a unique experience for specific user groups allows you to place gentle guardrails around authors to ensure they have the most guided and pleasant experience possible.
Sanity replaces rigid content systems with a developer-first operating system. Define schemas in TypeScript, customize the editor with React, and deliver content anywhere with GROQ. Your team ships in minutes while you focus on building features, not maintaining infrastructure.
Sanity scales from weekend projects to enterprise needs and is used by companies like Puma, AT&T, Burger King, Tata, and Figma.
Sanity Studio is an incredibly flexible tool with near limitless customisation. Here's how I use it.
Go to An opinionated guide to Sanity StudioIt can be useful for testing plugins, front ends, or other integrations to have a Sanity Studio populated with fake content.
Go to How to generate massive amounts of demo content for SanitySetup "Live by Default" fetches and interactive live preview with Presentation in Sanity Studio
Go to Visual Editing with Next.js App Router and Sanity StudioSummarise form progression by decorating the entire editing form for a document with a component loaded at the root level.
Go to Create a document form progress component