đź‘‹ Next.js Conf 2024: Come build, party, run, and connect with us! See all events
Last updated October 02, 2024

Restrict Access to Specific Documents

By Adam Gray

Ensure your editors can only publish content they have permission to by implementing document level access control.

Gotcha

This guide includes custom roles features available exclusively on Sanity’s Enterprise plans.

There are some use cases that require granular, down to the document, access control. Thanks to the flexibility of Sanity, this is entirely possible! We will achieve this by adding metadata to documents that we can then filter on.

Adding the Metadata

We’ll start by adding a new field to our document called allowedEditors. We want each document to track which users have the ability to edit, so we need to store an array of the 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 us the correct data structure for our field, but isn’t a great user experience (who wants to type in user IDs by hand!)

Allowed Editors UI with "user_id1" in the input field

We can improve the UX dramatically by installing the User Select Input plugin. Following the guide to install the plugin gives us access to the userSelect field type.

Let’s update our field type to use it.

defineField({
  name: "allowedEditors",
  type: "array",
  of: [{ type: "userSelect" }]
})
Allowed Editors Selector using the User Select Input Sanity Plugin

Much better. Now we can search for users with an intuitive interface.

Currently, all users can modify the “Allowed Editors” for our document. Let’s update the field type so that this field is hidden to all users, except for administrators.

defineField({
  name: "allowedEditors",
  type: "array",
  of: [{ type: "userSelect" }],
  hidden: ({ currentUser }) => currentUser.role !== "administrator",
})

Great! Our metadata field is set up. Next, we need to create a new content resource that will allow us to assign users to a role that can only modify documents they’ve been allowed to edit.

Creating a Content Resource

Define a new Content Resource by visiting sanity.io/manage, or using the CLI with:

npx sanity@latest manage

Then, selecting the project, navigating to “Access”, then “Resources” in the sidebar.

The content resources manage page

From here, create a new Content Resource. Feel free to name it whatever you like, I went with “Allowed to Edit”. In the “GROQ filter” section, define the filter that will return documents that the user has the ability to edit. We can use the identity GROQ function to achieve this. The identity function returns the ID of the current user. As our document stores an array of user IDs, this filter will check if the current user is one of those IDs.

identity() in allowedEditors

Attaching the Content Resource to a Custom Role

Now that we’ve created our Content Resource, apply it to a role by navigating to “Roles” by selecting it in the sidebar.

Access Roles

From here, if you already have a custom role you wish to apply this resource too, 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”.

Content permissions setting with "Allowed to Edit" content resource set to "Publish"

Users that are assigned this role will now only be able to publish documents that an administrator has allowed them to!

A Better Structure

Now that we’ve implemented both the data structure to store the allowed editors, and created the permissions, the only thing that’s left is to customize the structure tool so that editors only see documents they have the ability to edit.

This section will require some experience with Structure Builder, we have great documentation if you’ve not customized your structure before!

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.

function posts(S: StructureBuilder) {
  let documentFilter = "";

  if (S.context.currentUser?.role === "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));
}

With that, you now have a system to define editors for specific documents, along with a great user experience!

Sanity – build remarkable experiences at scale

Sanity Composable Content Cloud is the headless CMS that gives you (and your team) a content backend to drive websites and applications with modern tooling. It offers a real-time editing environment for content creators that’s easy to configure but designed to be customized with JavaScript and React when needed. With the hosted document store, you query content freely and easily integrate with any framework or data source to distribute and enrich content.

Sanity scales from weekend projects to enterprise needs and is used by companies like Puma, AT&T, Burger King, Tata, and Figma.