Sanity logosanity.ioAll Systems Operational© Sanity 2026
Change Site Theme
Sanity logo

Documentation

    • Overview
    • Platform introduction
    • Next.js quickstart
    • Nuxt.js quickstart
    • Astro quickstart
    • React Router quickstart
    • Studio quickstart
    • Build with AI
    • Content Lake
    • Functions
    • APIs and SDKs
    • Visual Editing
    • Blueprints
    • Platform management
    • Dashboard
    • Studio
    • Canvas
    • Media Library
    • App SDK
    • Content Agent
    • HTTP API
    • CLI
    • Libraries
    • Specifications
    • Changelog
    • User guides
    • Developer guides
    • Courses and certifications
    • Join the community
    • Templates
Developer guides
Overview

  • Develop with AI

    Best practices

  • Query optimization

    Paginating with GROQ
    High performance GROQ

  • Roles and authentication

    Setting up Single Sign-On with SAML
    Third-Party Login (Deprecated)
    OAuth2
    Set up SSO authentication with SAML and Azure/Entra ID
    Set up SSO authentication with SAML and PingIdentity
    Set up SSO authentication with SAML and JumpCloud
    Reconcile users against internal systems
    Restrict Access to Specific Documents
    Setting up a Default Relay State for IdP Initiated - SAML Logins

  • Structured content

    Scalable navigation patterns
    An opinionated guide to Sanity Studio
    Browsing Content How You Want with Structure Builder
    Deciding on fields and relationships
    Create richer array item previews
    Dynamic folder structure using the currentUser and workflow states
    Create a time duration object field
    Level up Your Edit Modal with Next/Previous Navigation Buttons for Array Items
    Create a “coupon generator” string field input
    Managing redirects with Sanity
    Create a document form progress component
    Create an array input field with selectable templates
    Creating a Parent/Child Taxonomy
    Create interactive array items for featured elements
    Create a visual string selector field input
    Create a survey rating number field input
    How to use structured content for page building
    Create a recycling bin for deleted documents via Sanity Functions

  • Frontend integration

    Add live content to your application
    Forms with Sanity
    Vercel Integration
    Build your blog with Astro and Sanity
    How to implement front-end search with Sanity

  • Ecommerce

    Displaying Sanity content in Shopify
    Sanity Connect for Shopify
    Custom sync handlers for Sanity Connect

  • Integrating with other services

    A/B testing with Sanity and Growthbook
    Cookie consent integrations with Sanity
    Integrating external data sources with Sanity
    Klaviyo (email campaigns)
    Developing with Next.js on GitHub Codespaces

  • Adopting Sanity

    How to pitch Sanity.io to your team
    Convincing your clients to go with Sanity.io, rather than a traditional CMS
    Not-profit plan
    Agencies: Navigating the Spring 2025 Organization Changes
    How to generate massive amounts of demo content for Sanity
    How to implement Multi-tenancy with Sanity

  • GROQ

    GROQ-Powered Webhooks – Intro to Filters
    GROQ-Powered Webhooks – Intro to Projections

  • Portable Text

    Presenting Portable Text
    Add Inline blocks for the Portable Text Editor
    Beginners guide to Portable Text
    How to add custom YouTube blocks to Portable Text
    Converting Inline Styles to Sanity Block Decorators
    Add things to Portable Text
    Change the height of the PTE

  • Community and ecosystem

    Create your own Sanity template
    Community guides
    Community Code of Conduct
    Contribute to the ecosystem

  • Plugin development

    Migrating plugins to support Content Releases

On this page

Previous

Reconcile users against internal systems

Next

Setting up a Default Relay State for IdP Initiated - SAML Logins

Was this page helpful?

On this page

  • Adding the Metadata
  • Creating a Content Resource
  • Attaching the Content Resource to a Custom Role
  • A Better Structure
Developer guidesLast updated September 24, 2025

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 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!)

Loading...
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" }]
})
Loading...
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
pnpm dlx sanity@latest manage
yarn dlx sanity@latest manage
bunx sanity@latest manage

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

Loading...
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.

Loading...
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”.

Loading...
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!

// 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'}],
    }),
  ],
})
Allowed Editors UI with "user_id1" in the input field
defineField({
  name: "allowedEditors",
  type: "array",
  of: [{ type: "userSelect" }]
})
Allowed Editors Selector using the User Select Input Sanity Plugin
defineField({
  name: "allowedEditors",
  type: "array",
  of: [{ type: "userSelect" }],
  hidden: ({ currentUser }) => currentUser.role !== "administrator",
})
npx sanity@latest manage
pnpm dlx sanity@latest manage
yarn dlx sanity@latest manage
bunx sanity@latest manage
pnpm dlx sanity@latest manage
yarn dlx sanity@latest manage
The content resources manage page
Access Roles
Content permissions setting with "Allowed to Edit" content resource set to "Publish"
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));
}