Creating custom user groups for conditional Studio visibility in Sanity

22 replies
Last updated: Apr 29, 2020
Am I right in concluding that an enterprise plan is required to create custom user groups to conditionally show sections of the Studio? Is there any other way to do this? I’m trying to figure out if my client will need 3 separate Sanity projects for a set of sites I’m building that are all under the same umbrella and share a lot of content. Ideally they all live in a single dataset, but users from each team only have access to their specific section of the studio.
Apr 28, 2020, 8:40 PM
SSO/Custom access control is API level and a enterprise feature. You can, however, configure the desk structure based on user info. Here’s a minimal example https://github.com/sanity-io/sanity-recipes/blob/master/snippets/deskStructureWithRoles.js
Apr 28, 2020, 8:43 PM
Thanks
Knut Melvær
! Changing desk structure based on role is all I need. Is there any way to create more user roles? So all my content editors would be at the same level hierarchically, but have access to different areas in the desk (Group A, Group B, Group C).
Apr 28, 2020, 8:45 PM
Not outside of doing that in code within the studio yourself. Actually creating custom user groups are part of custom access permissions.
Apr 28, 2020, 8:47 PM
That’s what I was afraid of
Apr 28, 2020, 8:47 PM
Isn’t that hard though :) you can create an array of objects that holds the structure, user ids and group name and resolve by matching it against the user object
Apr 28, 2020, 8:50 PM
When you say
doing that in code within the studio yourself
do you mean implementing the role based logic you pasted above (which is limited to Admin or Read/Write), or could I go further and cross reference the user email address against a list for each group stored in the dataset in order to determine desk structure visibility? If not, could this custom user groups feature be billed as an add on to one of the lower plans?
Apr 28, 2020, 8:52 PM
Whoops our messages crossed — I think I understand now! Pretty much as I described right? I could allow the “Admin” level user access to the User Groups structure to assign the permissions and then all “Read/Write” level users would be checked against those group arrays to determine structure visibility
Apr 28, 2020, 8:54 PM
Yup!
Apr 28, 2020, 8:54 PM
Amazing, thanks
Knut Melvær
, I’m gonna give it a shot today!
Apr 28, 2020, 8:54 PM
But it wouldn’t actually restrict them from accessing any document e.g through search etc. I guess you could apply the same logic in Document actions too. So not guaranteed bullet-proof
Apr 28, 2020, 8:55 PM
So it would be more nudging people away from certain sections I guess
Apr 28, 2020, 8:56 PM
But it wouldn’t actually restrict them from accessing any document e.g through search etc. I guess you could apply the same logic in Document actions too. So not guaranteed bullet-proof
Apr 28, 2020, 8:55 PM
Isn’t that hard though :) you can create an array of objects that holds the structure, user ids and group name and resolve by matching it against the user object
Apr 28, 2020, 8:50 PM
So it would be more nudging people away from certain sections I guess
Apr 28, 2020, 8:56 PM
Gotcha, I think that’s ok for my use case but good to know
Apr 28, 2020, 8:57 PM
When you say
doing that in code within the studio yourself
do you mean implementing the role based logic you pasted above (which is limited to Admin or Read/Write), or could I go further and cross reference the user email address against a list for each group stored in the dataset in order to determine desk structure visibility? If not, could this custom user groups feature be billed as an add on to one of the lower plans?
Apr 28, 2020, 8:52 PM
Whoops our messages crossed — I think I understand now! Pretty much as I described right? I could allow the “Admin” level user access to the User Groups structure to assign the permissions and then all “Read/Write” level users would be checked against those group arrays to determine structure visibility
Apr 28, 2020, 8:54 PM
Yup!
Apr 28, 2020, 8:54 PM
Amazing, thanks
Knut Melvær
, I’m gonna give it a shot today!
Apr 28, 2020, 8:54 PM
Knut Melvær
one last question for you on this — I’m setting a document type up for User Groups and only giving access to Administrators. Working great so far. The last step is populating the User Group array input with the project’s list of users. Is there any easy way to do this? I can also just manually set an array of email address and compare that to the
user.email
in the deskStructure.js, but selecting from available users would be really slick. I’m thinking there’s got to be a way with a reference field
Apr 28, 2020, 10:02 PM
Hm. You may be able to use the user store from the structure example for this? I’m not 100% sure tbh (it starts to get late at CET)
Apr 28, 2020, 10:05 PM
So I don’t think you can get a list of users from the
userStore
as imported from `'part:@sanity/base/user'`— it includes methods getUser and getUsers but you have to pass the user id as an argument. That said I was able to get this working by just comparing manually entered email addresses in a User Group array to the current user.
I was really tripped up by the rxjs observables syntax! I needed to get the currentUser object outside of the default export return. I ended up with this to set the currentUser:


let currentUser;
userStore.currentUser.pipe(take(1)).subscribe((value) => {
	currentUser = value.user;
});
I also needed the deskstructure return to be async so I could first use the client to fetch the admin created User Group arrays. What I ended up with feels a bit sloppy but it is working! And I may just hide the studio search bar for read/write role users.


import React from "react";
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 { take, map } from "rxjs/operators";

import {
	ConfigMenu,
	ProductMenuItem,
	ProductVariantParent,
	CollectionMenuItem,
	PageMenuItem,
} from "./structure/index";

let currentUser;
userStore.currentUser.pipe(take(1)).subscribe((value) => {
	currentUser = value.user;
});

const sanityClient = require("@sanity/client");

const client = sanityClient({
	projectId: "6p7e2mco",
	dataset: "production",
	useCdn: false, // `false` if you want to ensure fresh data
	withCredentials: true,
});

const queryUserGroups = `*[_type == "userGroup"] {
			...,
    }`;

//
// === Structure ===
//
const adminStructure = S.list()
	.title("Content")
	.items([
		ConfigMenu,
		PageMenuItem,
		CollectionMenuItem,
		ProductMenuItem,
		ProductVariantParent,
	]);

const defaultStructure = S.list()
	.title("Content")
	.items([
		PageMenuItem,
		CollectionMenuItem,
		ProductMenuItem,
		ProductVariantParent,
	]);

const group001Structure = S.list()
	.title("Content")
	.items([PageMenuItem, CollectionMenuItem, ProductMenuItem]);

const group002Structure = S.list()
	.title("Content")
	.items([PageMenuItem, ProductMenuItem, ProductVariantParent]);

const getStructure = async () => {
	let userGroups;
	await client
		.fetch(queryUserGroups)
		.then((response) => {
			userGroups = response;
		})
		.catch((error) => {
			console.log("problem", error);
		});

	const { role, email } = currentUser;
	const currentUserGroups = userGroups.filter((userGroup) => {
		const { items } = userGroup;
		return items.some((item) => item.userEmail == email);
	});

	// Administrator structure
	if (role === "administrator") {
		return adminStructure;
	}

	// User Group structures
	if (
		currentUserGroups &&
		currentUserGroups.some((group) => group._id == "001users")
	) {
		return group001Structure;
	} else if (
		currentUserGroups &&
		currentUserGroups.some((group) => group._id == "002users")
	) {
		return group002Structure;
	}

	// Fallback default read/write role structure
	return defaultStructure;
};

export default getStructure;
Apr 29, 2020, 6:13 PM

Sanity– build remarkable experiences at scale

The Sanity Composable Content Cloud is the headless CMS that treats content as data to power your digital business. Free to get started, and pay-as-you-go on all plans.

Was this answer helpful?