Creating a Parent/Child Taxonomy - has 5 likes
Create common taxonomy schemas like Categories and Tags with parent/child relationships
Go to Creating a Parent/Child TaxonomyThe 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".
// ./src/desk-structure.js
import S from '@sanity/desk-tool/structure-builder'
import userStore from 'part:@sanity/base/user'
// remember to add rxjs/operators to your dependencies with npm or yarn
import {map} from 'rxjs/operators'
const authorItems = [
S.documentTypeListItem('article').title('Articles'),
S.documentTypeListItem('author').title('Authors'),
]
const adminOnly = [
S.documentTypeListItem('project').title('Projects'),
S.documentTypeListItem('setting').title('Settings'),
]
export default () =>
userStore.me.pipe(
map((user) => {
const title = `${user.name} Content`
const userIsAdmin = user.roles.find(
(role) => role.name === `administrator`
);
const items = userIsAdmin
? [...authorItems, S.divider(), ...adminOnly]
: authorItems;
return S.list().title(title).items(items)
})
)
In addition to customising which Desk Structure items are shown, we can customise what items are shown in a Document List based on GROQ's identity()
function.
identity()
is 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('author')
.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 article
type documents to pre-fill an author
field with the User ID.
import T from '@sanity/base/initial-value-template-builder'
import userStore from 'part:@sanity/base/user'
const getCurrentUser = () => {
let userDetails
userStore.me.subscribe((user) => {
userDetails = user.id ? user : undefined
})
return userDetails
}
const defaults = T.defaults()
export default [
// Filter out the default `author` template
...defaults.filter((template) => template.spec.id !== `author`),
// Insert our custom template so all `author` documents have a `userId` field
T.template({
id: 'authorWithId',
title: 'Author with ID',
schemaType: 'author',
value: {
userId: getCurrentUser().id,
},
}),
]
You might also like to...
author
field readOnly
so the data is not corruptedA note on "Authors" vs "Users"
It is possible and likely your schema will include something like an author
type document. Or a greater abstraction; a person
document that can be assigned a value of "author" either as a string
or reference
.
These are different to Sanity Users, 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.
Now while we have hidden sections 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 customised Initial Value Templates to remove the default new author
document template. We could use the same logic to remove specific document types from this menu by filtering the T.defaults()
as below
import T from '@sanity/base/initial-value-template-builder'
import userStore from 'part:@sanity/base/user'
const getCurrentUser = () => {
let userDetails
userStore.me.subscribe((user) => {
userDetails = user.id ? user : undefined
})
return userDetails
}
const defaults = T.defaults()
const currentUser = getCurrentUser()
const userIsAdmin = currentUser.roles.find((role) => role.name === `administrator`)
export default [
...defaults
// Filter out the default `author` template for the custom one below
.filter((template) => template.spec.id !== `author`)
// Only administrators can create `project` and `setting` documents
.filter((template) =>
userIsAdmin ? true : ![`project`, `setting`].includes(template.spec.id)
),
// Insert our custom template so all `author` documents have a `userId` field
T.template({
id: 'authorWithId',
title: 'Author with ID',
schemaType: 'author',
value: {
userId: currentUser.id,
},
}),
]
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.
{
name: 'price',
type: 'number',
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 {
name: 'article',
type: 'document',
readOnly: ({currentUser}) => !currentUser.roles
.find((role) => ['administrator', 'editor'].includes(role.name)),
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.
Sanity's Roles and Permissions API will take care of ensuring 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 selectively hide or disable the Action.
Because Document Actions happen inside React scope, we can move our userStore logic into a custom hook.
Here's a code example that hides the custom Action for anyone other than Administrators or Editors. And disables the button for anyone other than Administrators.
import userStore from 'part:@sanity/base/user'
import { useEffect, useState } from 'react'
export function useCurrentUser() {
const [user, setUser] = useState()
useEffect(() => {
userStore.currentUser.subscribe(e => setUser(e.user))
}, [])
if (!user) {
return {}
}
return user
}
export default function CreateMilestone(props) {
const user = useCurrentUser();
// Hide the button for users who are not "editor" or "administrator"
if (
!user?.roles?.map((role) => ["administrator", "editor"].includes(role.name))
) {
return null;
}
const isAdmin = user.roles.find((role) => role.name === "administrator");
// Disable the button for non-"administrator" users
return {
disabled: !isAdmin,
label: "Create Milestone",
onHandle: async () => {
// ...your function logic
props.onComplete();
},
};
}
On Enterprise plans, Studio experience can be customised with Content Resources.
Going back to our userId
example earlier, it's possible 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.
Being able to configure 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.
Create common taxonomy schemas like Categories and Tags with parent/child relationships
Go to Creating a Parent/Child TaxonomyCombine Sanity's blazing-fast CDN with Remix's cached at the edge pages.
Go to How to build a Remix website with Sanity.io and live previewSometimes the content you need to reference lives outside of Sanity
Go to Creating a custom input to display and save third party dataA step-by-step guide to setup Next.js and Sanity Studio with Live Preview
Go to Live Preview with Next.js and Sanity.io: A Complete Guide