Control content fields and tabs visibility in Umbraco CMS

With the flexible architecture of Umbraco v8, it's feasible to tailor the back-office experience to different user roles. Here, we'll walk through how to hide or show specific content fields and tabs based on user roles.

Prerequisites#

  • A working Umbraco CMS v8 setup.
  • Basic understanding of Umbraco's back office.
  • Familiarity with C# and Umbraco's APIs.

Role-Based Customization of Umbraco CMS Back-Office Interface#

The purpose is to provide content managers, writers, and technical teams with a simplified and clutter-free back office interface by showing or hiding specific fields and tabs.

To ensure that they only see content types and fields relevant to their role.

Include these essential namespaces#

using Umbraco.Web.Editors;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.PublishedModels;
using Umbraco.Core.Composing;

Create the Composer and Component#

The custom composer will append our component to Umbraco's component collection during the application's startup.

[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
public class HideContentPartsComposer : IUserComposer
{
    public void Compose(Composition composition)
    {
        composition.Components().Append<HideContentPartsComponent>();
    }
}

Now, create the component that will handle the logic for showing or hiding content based on the user role.

public class HideContentPartsComponent: IComponent
{
    public void Initialize()
    {
        throw new NotImplementedException();
    }

    public void Terminate()
    {
        throw new NotImplementedException();
    }
}

Register SendingContentModel event#

Secondly, go to HideContentPartsComponent and set the SendingContentModel event for EditorModelEventManager as follows:

public void Initialize()
{
    EditorModelEventManager.SendingContentModel += EditorModelEventManager_SendingContentModel;
}

private void EditorModelEventManager_SendingContentModel(HttpActionExecutedContext sender, EditorModelEventArgs<ContentItemDisplay> e)
{
   //Restrict visibility here
}

Finding user groups for logged-in Umbraco user#

To get user roles in Umbraco, you need to refer to the CurrentUser object inside UmbracoContext as follows: 

IList<string> _currentUserGroupsAliasses = 
    e.UmbracoContext.Security.CurrentUser?.Groups?.Select(userGroup => userGroup.Alias).ToList();

Showing content fields only for the role#

private void ShowFieldOnlyForRole(ContentItemDisplay contentItemDisplay, string contentTypeAlias, string propertyAlias, string userGroup)
{
    if (contentItemDisplay.ContentTypeAlias.Equals(contentTypeAlias))
    {
        if (!_currentUserGroupsAliasses.IsNullOfEmpty() && !_currentUserGroupsAliasses.Contains(userGroup))
        {
            foreach (var tab in contentItemDisplay?.Variants?.FirstOrDefault()?.Tabs?.ToList())
            {
                if (!tab.Properties.IsNullOfEmpty())
                {
                    tab.Properties = tab.Properties.Where(x => x.Alias != propertyAlias);
                }
            }
        }
    }
}

For example, to display the 'google manager' field exclusively for administrators, use the following method:

/* Show google manager field only for admin */
ShowFieldOnlyForRole(contentItemDisplay: e.Model, contentTypeAlias: "globalSettings", 
    propertyAlias: "googleTagManagerId", userGroup: "admin");
Showing googleTagManagerId field for admin role

Showing googleTagManagerId field for admin role

Showing content tabs only for the role#

private void ShowTabOnlyForRole(ContentItemDisplay contentItemDisplay, string contentTypeAlias, string tabAlias, string userGroup)
{
    if (contentItemDisplay.ContentTypeAlias.Equals(contentTypeAlias))
    {
        if (!_currentUserGroupsAliasses.IsNullOfEmpty() && !_currentUserGroupsAliasses.Contains(userGroup))
        {
            if ((contentItemDisplay?.Variants?.FirstOrDefault()?.Tabs?.Any()).GetValueOrDefault(false))
            {
                contentItemDisplay.Variants.FirstOrDefault().Tabs =
                    contentItemDisplay.Variants.FirstOrDefault().Tabs.Where(x=> x.Alias != tabAlias);
            }
        }
    }
}

To show the ‘SEO Settings’ tab only for the admin role execute the method as follows:

/* Show 'SEO Settings' tab only for admin role */
ShowTabOnlyForRole(contentItemDisplay: e.Model, contentTypeAlias: "globalSettings", 
    tabAlias: "SEO Settings", userGroup: "admin");
Showing SEO Settings tab only for admin role

Showing SEO Settings tab only for admin role

Show content tab only for specific roles#

To make ‘SEO Settings’ tab visible only for both admin and editor roles, execute the method as follows:

ShowTabOnlyForRoles(contentItemDisplay: e.Model, contentTypeAlias: "globalSettings", 
     tabAlias: "SEO Settings", userGroups: new []{ "admin", "editor"  } );
private void ShowTabOnlyForRoles(ContentItemDisplay contentItemDisplay, string contentTypeAlias, string tabAlias, string[] userGroups)
{
    if (contentItemDisplay.ContentTypeAlias.Equals(contentTypeAlias))
    {
        if (!_currentUserGroupsAliasses.IsNullOfEmpty() && !userGroups.IsNullOfEmpty() && !_currentUserGroupsAliasses.Any(cug=> userGroups.Any(ug=>ug == cug)))
        {
            if ((contentItemDisplay?.Variants?.FirstOrDefault()?.Tabs?.Any()).GetValueOrDefault(false))
            {
                contentItemDisplay.Variants.FirstOrDefault().Tabs =
                    contentItemDisplay.Variants.FirstOrDefault().Tabs.Where(x=> !x.Alias.Equals(tabAlias));
            }
        }
    }
}

Show list of tabs only for specific roles #

To make ‘SEO Settings’ and “Content” tab visible only for both admin and editor user groups, execute the method as follows:

ShowTabsOnlyForRoles(contentItemDisplay: e.Model, contentTypeAlias: "globalSettings", 
    tabAliases: new []{ "Content", "SEO Settings"} , userGroups: new []{ "admin", "editor"  } );
private void ShowTabsOnlyForRoles(ContentItemDisplay contentItemDisplay, string contentTypeAlias, string[] tabAliases, string[] userGroups)
{
    if (contentItemDisplay.ContentTypeAlias.Equals(contentTypeAlias))
    {
        if (!_currentUserGroupsAliasses.IsNullOfEmpty() && !userGroups.IsNullOfEmpty() && !_currentUserGroupsAliasses.Any(cug=> userGroups.Any(ug=>ug == cug)))
        {
            if ((contentItemDisplay?.Variants?.FirstOrDefault()?.Tabs?.Any()).GetValueOrDefault(false))
            {
                contentItemDisplay.Variants.FirstOrDefault().Tabs =
                    contentItemDisplay.Variants.FirstOrDefault().Tabs.Where(x=> !tabAliases.Contains(x.Alias));
            }
        }
    }
}

Helper Extensions#

We'll include an extension method to streamline the code.

using System.Collections.Generic;

public static class CollectionsExtensions
{
    public static bool IsNullOrEmpty<T>(this ICollection<T> collection)
    {
        return collection == null || !collection.Any();
    }
}

Working HideContentPartsComponent implementation#

public class HideContentPartsComponent: IComponent
{
    IList<string>  _currentUserGroupsAliasses;

    public void Initialize()
    {
        EditorModelEventManager.SendingContentModel += EditorModelEventManager_SendingContentModel;
    }
   
private void EditorModelEventManager_SendingContentModel(HttpActionExecutedContext sender, EditorModelEventArgs<ContentItemDisplay> e)
    {
        _currentUserGroupsAliasses = e.UmbracoContext.Security.CurrentUser?.Groups?.Select(userGroup => userGroup.Alias).ToList();
      
        /* Show google manager field only for admin */
        ShowFieldOnlyForRole(contentItemDisplay: e.Model, contentTypeAlias: "globalSettings", 
            propertyAlias: "googleTagManagerId", userGroup: "admin");
        
        /* Show 'SEO Settings' tab only for admin role */
        ShowTabOnlyForRole(contentItemDisplay: e.Model, contentTypeAlias: "globalSettings", 
            tabAlias: "SEO Settings", userGroup: "admin");
    }

private void ShowFieldOnlyForRole(ContentItemDisplay contentItemDisplay, string contentTypeAlias, string propertyAlias, string userGroup)
{
    if (contentItemDisplay.ContentTypeAlias.Equals(contentTypeAlias))
    {
        if (!_currentUserGroupsAliasses.IsNullOfEmpty() && !_currentUserGroupsAliasses.Contains(userGroup))
        {
            foreach (var tab in contentItemDisplay?.Variants?.FirstOrDefault()?.Tabs?.ToList())
            {
                if (!tab.Properties.IsNullOfEmpty())
                {
                    tab.Properties = tab.Properties.Where(x => x.Alias != propertyAlias);
                }
            }
        }
    }
}

private void ShowTabOnlyForRole(ContentItemDisplay contentItemDisplay, string contentTypeAlias, string tabAlias, string userGroup)
{
    if (contentItemDisplay.ContentTypeAlias.Equals(contentTypeAlias))
    {
        if (!_currentUserGroupsAliasses.IsNullOfEmpty() && !_currentUserGroupsAliasses.Contains(userGroup))
        {
            if ((contentItemDisplay?.Variants?.FirstOrDefault()?.Tabs?.Any()).GetValueOrDefault(false))
            {
                contentItemDisplay.Variants.FirstOrDefault().Tabs =
                    contentItemDisplay.Variants.FirstOrDefault().Tabs.Where(x=> x.Alias != tabAlias);
            }
        }
    }
}

private void ShowTabOnlyForRoles(ContentItemDisplay contentItemDisplay, string contentTypeAlias, string tabAlias, string[] userGroups)
{
    if (contentItemDisplay.ContentTypeAlias.Equals(contentTypeAlias))
    {
        if (!_currentUserGroupsAliasses.IsNullOfEmpty() && !userGroups.IsNullOfEmpty() && !_currentUserGroupsAliasses.Any(cug=> userGroups.Any(ug=>ug == cug)))
        {
            if ((contentItemDisplay?.Variants?.FirstOrDefault()?.Tabs?.Any()).GetValueOrDefault(false))
            {
                contentItemDisplay.Variants.FirstOrDefault().Tabs =
                    contentItemDisplay.Variants.FirstOrDefault().Tabs.Where(x=> !x.Alias.Equals(tabAlias));
            }
        }
    }
}

private void ShowTabsOnlyForRoles(ContentItemDisplay contentItemDisplay, string contentTypeAlias, string[] tabAliases, string[] userGroups)
{
    if (contentItemDisplay.ContentTypeAlias.Equals(contentTypeAlias))
    {
        if (!_currentUserGroupsAliasses.IsNullOfEmpty() && !userGroups.IsNullOfEmpty() && !_currentUserGroupsAliasses.Any(cug=> userGroups.Any(ug=>ug == cug)))
        {
            if ((contentItemDisplay?.Variants?.FirstOrDefault()?.Tabs?.Any()).GetValueOrDefault(false))
            {
                contentItemDisplay.Variants.FirstOrDefault().Tabs =
                    contentItemDisplay.Variants.FirstOrDefault().Tabs.Where(x=> !tabAliases.Contains(x.Alias));
            }
        }
    }
}

    public void Terminate()
    {
        EditorModelEventManager.SendingContentModel -= EditorModelEventManager_SendingContentModel;
    }
}

Common pitfall and workaround when hiding properties/tabs in Umbraco#

However, not everything works beautifully, as the Umbraco documentation says. 

Wojciech Tengler noticed a bug. 

When submitting content, hidden properties might unexpectedly save as empty values.

To prevent this, instead of just hiding the properties, consider changing the view.

Here's an example in pseudo-code:

var tabAlias = "tabAliasToHide";

foreach(var variant in model.Variants) {
    var tabsToHide = variant.Tabs.Where(t => t.Alias.Equals(tabAlias));

    foreach(var tab in tabsToHide) {
        foreach(var propety in tab.Properties) {
            propety.View = "/App_Plugins/CustomPropertyEditorViews/PropertyWithoutAccess.html";
        }
    }
}

Conclusion#

Umbraco's extensible nature means it can be tailored to specific requirements, as demonstrated by this role-based visibility control.

By understanding and leveraging Umbraco's events and APIs, you can create an intricate content control system that enhances productivity, security, and user experience.

You can modify or extend the above code as needed, and I recommend exploring the Umbraco events if you need more advanced back-office workflows.

📚 For more tips and updates, don't forget to browse our blog. 👩‍💻👨‍💻

↑ Top ↑