Restrict access to specific documents
Create document-level access controls with Sanity
This developer guide was contributed by Adam Gray (Solution Architect at Sanity).
Ensure your editors can only publish content they have permission to by implementing document-level access control.
This is a paid feature
This feature is available on certain Enterprise plans. Talk to sales to learn more.
Some use cases need access control at the level of an individual document. You can build this by adding metadata to your documents and then filtering on it.
Prerequisites
- A project on the Enterprise plan. Custom roles and custom content resources are Enterprise features.
- Permission to manage members and roles in the project. The default Administrator role includes it.
- The
sanity-plugin-user-select-inputplugin installed in your studio and registered insanity.config.ts. - A deployed studio schema, or a GROQ filter you write by hand. The content resource builder reads your deployed schema to list document types.
Add the metadata
Start by adding a new field to your document called allowedEditors. Each document needs to track which users can edit it, so store an array of user IDs.
One approach is to create an array of strings:
// schemaTypes/postType.ts
import {defineField, defineType} from 'sanity'
export const postType = defineType({
type: 'document',
name: 'post',
title: 'Post',
fields: [
// ...all other fields
defineField({
name: 'allowedEditors',
type: 'array',
of: [{type: 'string'}],
}),
],
})This gives you the correct data structure, but it is a poor editing experience, because someone has to type user IDs by hand.
You can improve the editing experience by installing the User Select Input plugin. Following the plugin’s installation guide gives you access to the userSelect field type.
Update the field type to use it:
defineField({
name: "allowedEditors",
type: "array",
of: [{ type: "userSelect" }]
})Now you can search for users with an intuitive interface.
Currently, all users can modify Allowed Editors on your document. Update the field type so that the field is hidden to everyone except administrators.
import {userHasRole} from 'sanity'
defineField({
name: "allowedEditors",
type: "array",
of: [{ type: "userSelect" }],
hidden: ({ currentUser }) => !userHasRole(currentUser, "administrator"),
})The hidden property only controls whether the field appears in the Studio form. It is not a permission boundary: anyone who can write to the document can still set allowedEditors through the API. The custom role you create in the next section is what enforces access.
The metadata field is now set up. Next, create a content resource that lets you assign users to a role that can only modify documents they have been allowed to edit.
Create a content resource
Define a new content resource by visiting sanity.io/manage, or using the CLI with:
npx sanity@latest managepnpm dlx sanity@latest manageyarn dlx sanity@latest managebunx sanity@latest manageIf you opened sanity.io/manage in a browser, select your project first. The CLI command opens your project directly. In either case, select Access › Resources in the sidebar.
From here, create a new content resource. Name it whatever you like; this guide uses Allowed to Edit. In the GROQ filter section, define the filter that returns the documents the user can edit. Use the identity GROQ function to achieve this. The identity function returns the project user ID of the current user. Because the document stores an array of user IDs, this filter checks whether the current user is one of them.
identity() in allowedEditorsAttach the content resource to a custom role
Now that the content resource exists, apply it to a role by selecting Access › Roles in the sidebar.
From here, if you already have a custom role you want to apply this resource to, select it. If not, create a new role.
In the Content permissions section, select Edit, then set the permissions for your content resource to Publish.
Users assigned this role can now publish only the documents an administrator has allowed them to edit.
A better structure
The data structure and the permissions are now in place. The last step is to customize the Structure Tool so that editors only see the documents they can edit.
This section requires some experience with Structure Builder. If you have not customized your structure before, see Get started with Structure Builder API.
The following function defines a new list item that shows all posts if the user is an administrator, and only editable posts if the user is not.
import {userHasRole} from 'sanity'
import {type StructureBuilder} from 'sanity/structure'
function posts(S: StructureBuilder) {
let documentFilter = "";
if (userHasRole(S.context.currentUser, "administrator")) {
documentFilter = '_type == "post"';
} else {
documentFilter = '_type == "post" && identity() in allowedEditors';
}
return S.listItem()
.title("Posts")
.id("posts")
.child(S.documentList().title("Posts").filter(documentFilter));
}You now have a system for defining which editors can work on which documents.
Was this page helpful?
Feedback submitted, thanks for helping us improve the docs. Have you tried these alternatives?