How to control content fields and tabs visibility in Umbraco CMS

Hiding properties and tabs in Umbraco CMS

This post illustrates the idea of how you can control and restrict the visibility of content (node) parts, such as fields and properties for Umbraco back-office users (based on the role).
While Umbraco allows you to restrict the whole node to specific user groups, sometimes, this may not be enough, and you need a more advanced workflow. Luckily, Umbraco gives powerful API and events to manipulate the content node.

Real-life examples:

  • You have an article document with several fields, and you want to show ‘Publish Date’ only for the chief editor who is aware of scheduling and content planning.
  • You have pages with meta fields for SEO optimization, and you want to show ‘Title’ and ‘Description’ properties only for SEO expert.

We will walk through the simple implementation that can be a good reference for custom development and can be modified/extended to fit your needs.

You will learn the basics of how to:

  • Implement method making tab visible only for a specific role with given contentTypeAlias, propertyAlias, userGroup.
  • Implement method making field visible only for a specific role with given contentTypeAlias, tabAlias, userGroup.
  • Register the methods using composers so the changes affect the back-office.
  • Work with ContentItemDisplay object and its properties to render node based on business rules.

For the below implementation you need to include the following namespaces:

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

Create Composer and component

First, we need to create a Umbraco composer and component to register the custom implementation and visibility restrictions.

Composer:

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

Component:

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);
                }
            }
        }
    }
}

To show the google manager field only for admin execute the method as follows:

/* Show google manager field only for admin */
ShowFieldOnlyForRole(contentItemDisplay: e.Model, contentTypeAlias: "globalSettings", 
    propertyAlias: "googleTagManagerId", userGroup: "admin");
Umbraco Hide Property Field
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");
Umbraco Hide Content Tab
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));
            }
        }
    }
}

Wrapped code

To make the code leaner we’ll use a custom extension method that checks whether the collection is empty: 

public static class CollectionsExtensions
{
   public static bool IsNullOfEmpty<T>(this IEnumerable<T> list)
   {
      if (list == null)
         return true;

      if (list.Any() == false)
         return true;

      return false;
   }
}

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 when hiding properties/tabs in Umbraco

However, not everything works beautifully as the Umbraco documentation says. Wojciech Tengler noticed a bug. It may happen that your hidden properties will be overridden with empty values. If so, instead of hiding properties, it’s better to swap the view. Below is a 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";
        }
    }
}

Final words

Restricting Umbraco Content parts is easy and prevents users from mistakenly breaking the layout or losing data. Besides that, it makes the back-office cleaner and more user-friendly. Unauthorized persons will not be able to update the restricted content parts because fields/tabs will be hidden.

We went through the simple example of how to implement custom logic and hide/show parts for specific roles. 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. 

Similar Posts