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.

Developers can build upon this foundation, making the back office experience even more intuitive and user-friendly.

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

📞 Connect With Us! Are there more melodies of digital strategy you wish to explore?

Reach out to us for a duet! Our team is eager to share insights, discuss your needs, and find the perfect rhythm for your business.

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

↑ Top ↑