Being of the main contributors of Strapi (the most advanced open-source headless CMS to manage your content with no effort.) I'm often asked on Slack:
Community member: Hey Cyril, do you have an ETA on that Administrators Roles Plugin? I really need that feature for my client...
Me: Man we're stabilising the framework and making it way more extensible than it already is, so honestly, I have no idea.
Well last weekend I was feeling the need of trying to find a pure front-end solution to hide plugins and content types depending on the admin's role so the Strapi user can answer to their clients needs.
The idea behind this article is to provide a quick way to hide some views for a specific user's admin role. However, since this is a front-end hack, I need to state that the back-end won't make any difference between the "sub-roles" set for the administration panel (so it's really not secure) and such logic might need to be applied in both the Strapi's UI and your client's site.
We won't provide any support for bugs related to this article and since the codebase evolves really fast, this logic is really likely to change (it won't be really stable if I make a minor modification into the admin).
Now that everything has been said, let's dig into some practical example!
1️⃣ In a terminal window, clone the repository.
git clone git@github.com:strapi/strapi.git
2️⃣ Then launch the setup.
1cd strapi && npm run setup
3️⃣ Open a new terminal window, create a new project and set the desired database.
1strapi new blog --dev
4️⃣ Start the server.
1cd blog && strapi start
5️⃣ In a new terminal window, launch the front-end server.
1cd blog/admin && npm start
6️⃣ Go to localhost:4000/admin.
In this tutorial we will create a blog that contains articles. Each article is linked to several tags and vice versa, one category is linked to several categories. To create these content types we will use the Content-type Builder plugin that is already installed in each generated project.
Go to the Content Type Builder plugin and create the following content-types.
👉 Article
name
(string)description
(text): if you want the Rich Text Feature enabled visit the Advanced Settings Tab and select Display as WYSIWYG
Then, click on the Save button and the Content Type should appear in the left menu. Repeat the process for the others content-types.
👉 Tag
name
(string)articles
(many-to-many relation with Article
)👉 Category
title
(string)articles
(many-to-one relation with Article
)At this point, you should have 4 models displayed in the left menu. BTW, I'm always amazed by how fast Strapi is to create an API.
Now that the blog is created I'm going to explain my approach to hide and block some plugins depending on the admin user's role.
Currently, only admin users can access the administration panel, those admin users have all rights : they can create/delete/modify both Content Types and Data, set up permissions and modify the app's settings.
In order to hide some parts of the administration panel we need to add a field to the existing User Model like admin_layout
that we're going to use to pimp up our UI. When the user will logs in, the front-end will retrieve this "sub role" and depending on it, will remove elements or functionalities from the front-end.
The created "sub role" will only affect the front-end, for the back-end those admin users will still have all the rights so you might need to make the same approach in your client application.
For my blog API I want to create three "sub roles" : admin, author and editor
. To do so, go to the User model in the content-type-builer and add a new enumeration field called admin_layout
.
👉 User
admin_layout
(enumeration, required): admin,author,editor
.Don't forget to visit to the Advanced Settings Tab, click on required
and save the modifications. Finally, your model should look like:
We have now created a specific field
admin_layout
that is necessary for the admin to hide elements from the menu.The only problem now is that the first registered user (you since you started with a fresh project) doesn't have this property so, we're going to modify the
users-permissions
plugin.
Since we created a dev
project for the sake of the example we need to make sure the first registered user has the admin_layout
"sub role" of type admin for your production one. So we need to set this default role from the front-end of the users-permissions
plugin.
With this modification the first registered user will have the admin
sub role.
At this point, we have just created our API and added a new field to the existing User model. We are now going to see how to hide some plugins and then, how to hide Content Types and views depending on the admin user's sub role.
In the first part we're going to tackle the question of hiding/removing some plugins depending on the user's role. Here what we want to do is delete a plugin from the UI only (it will still remain active in the back-end) and we will handle the special case of hiding the Users & Permissions one.
At some point we need to tell the administration which plugins to hide for a specific user "sub role". To do so, we need to create an adminLayout.js
file where we will write the UI's permissions and that the front-end will retrieve. Since this is a pure front-end "hack" the back-end configuration is pretty easy.
1- Create the adminLayout.js
file.
2- Send it to the front-end.
This step is the only back-end configuration that we need to do, the rest will only be a front-end hack...
Now that our back-end is ready we need to undertake some complex front-end modifications. The first one is in the /admin
folder. We are going to modify the following elements AdminPage, App, AppLoader, LeftMenuLinkContainer, Logout.
Here we just need to make a minor update in order to tell the AppLoader
container to stop showing a loader if a plugin is deleted from our store. Since there will be a difference between the number of plugins that the front-end should load and the ones that are loaded if we don't update this part the application will show a loader indefinitely.
Before getting any further we have to update the shouldLoad
instance of this container.
In this section we're are going :
adminLayout
adminLayout
property in our saga1️⃣ Create a new constant and action in the AdminPage container
2️⃣ Update our current reducer with a new key adminLayout
3️⃣ Create a new selector so we can retrieve the adminLayout
property in our saga
4️⃣ Update the saga in order to retrieve the adminLayout
and hide the desired plugins
5️⃣ Modifying the way we load our plugins
At this point you may have noticed that we handle a special case we trying to hide the users-permissions
plugin for a specific role. This is because if you delete this plugin from our main store the authentication won't be applied in our front-end so the user won't be able to make any request to the backend. In order to hide this plugin in the application we're going to update the LeftMenuLinkContainer
component. We already handled the case of trying to access this plugin using the url since it will display the BlockerComponent
.
The idea here is just to remove the link from the menu.
After all this modifications, we're facing a new issue: how to reset all our modification if a user logs out and logs in with a new account? We have to "reset" our layout and the easiest way is to reload our application.
What if we want to hide some Content Types and maybe give access to the list view but prevent an admin user from creating a new entry?
To do so, we need to add new keys to the adminLayout.js
file so the front-end will know which content types to show and which create views to block
Here we need to add two new keys to our adminLayout.js
user's role : contentTypesToHide
and contentTypeToReadOnly
, you'll need to make the modifications and restart your Strapi server.
In this part, we will not only hide content types from the left menu but also prevent some view from being accessed by using our BlockerComponent
.
Before digging into some code we need to understand how the content types' links are displayed in the left menu. Here what happens is that when the content-manager
is mounted in our application his bootstrap.js
file is called and it inserts into our main store a plugin
property containing the leftMenuSections
which has all our content types' link. Then, the LeftMenuLinkContainer
"search" this property in all our plugins to display the different links.
Like we did in the first part of this tutorial we will modify the AdminPage
's saga so it updates these keys by removing the desired content types from one plugins's object. To make the front-end a bit more secure not only will we delete the content types from the left menu but also we will make sure a content type can't be accessed (if said so) by typing the url directly into the browser.
To do so, we will create an object ctmAdminLayout
in the AdminPage
's reducer that we're going to pass to the content-manager
using the old React context API.
The admin is using the first context api and I will update it to the last one soon so you might need to make some further modifications.
Here are the elements that we need to modify:
Since we're want to store a new property in the reducer we need to create a new constant, action and add it into the reducer.
1️⃣ Create a new constant:
2️⃣ Create a new action:
3️⃣ Update the reducer:
At this point nothing is happening in our application the User model is still displaying even though we have set it to be hidden in our adminLayout.js
file. Like we did in the part one we are going to develop the logic in the saga
.
4️⃣ Update the saga to remove the content types from the left menu:
Finally we need to pass the ctmAdminLayout
object to the content-manager
plugin.
5️⃣ Update the index.js
The code from above will only remove the corresponding content types from the left menu. We now need to modify the content manager to make the appropriate views unaccessible by setting directly the URL in the browser.
As we did with the users-permissions
plugin we are going to use the BlockerComponent
to prevent a user from accessing a view that he can't.
As you may have seen I have introduced a contentTypestoReadOnly
key in the adminLayout.js
file it allows a user to just display the data in the ListPage
container. So if this condition is met, we need to hide the create button first and then block the view if he tries to access a content type by URL.
In this part we only to update two containers in the content-manager
:
ListPage
EditPage
with the same logic:1️⃣ Update the ListPage container:
2️⃣ Update the EditPage container:
Well, we did a lot of modifications but I think that you can extend this logic to fit your needs. As a reminder the code might need to be updated with the releases but it will be a temporary solution until we release the Admin & Permissions
plugin.