Umbraco uses a flexible user group and permissions system to control Backoffice access. This guide covers default user groups, section permissions, content-level permissions, custom group creation, and .NET-based permission management.
Understanding Umbraco Permissions Model
User Groups vs Members
User Groups (Backoffice):
- Control access to Umbraco Backoffice (
/umbraco) - Manage content, media, and settings
- Assigned via Users section
- This guide focuses on user groups
Member Groups (Front-end):
- Control access to website content
- Front-end user authentication
- Separate permission system
Permission Levels
Umbraco permissions operate at multiple levels:
- Section Access - Which Backoffice sections user can access
- Content Permissions - What actions user can perform on content
- Node-Level Permissions - Which content nodes user can see/edit
- Document Type Permissions - Which content types user can create
Default User Groups
Administrators
Full access to all Backoffice features.
Section Access:
- Content
- Media
- Settings
- Packages
- Users
- Members
- Forms
- Translation
Content Permissions:
- Browse Node (F)
- Create (C)
- Update (A)
- Delete (D)
- Publish (U)
- Copy (O)
- Move (M)
- Sort (S)
- Send to Publish (H)
- Permissions (R)
- Audit Trail (Z)
- Culture and Hostnames (I)
- Public Access (P)
- Notifications (N)
Use Cases:
- System administrators
- Senior developers
- Agency owners
- Full CMS control needed
Editors
Content management without system settings access.
Section Access:
- Content
- Media
- Forms (optional)
Content Permissions:
- Browse Node (F)
- Create (C)
- Update (A)
- Delete (D)
- Publish (U)
- Copy (O)
- Move (M)
- Sort (S)
- Notifications (N)
Use Cases:
- Content managers
- Marketing teams
- Editorial staff
- Daily content operations
Writers
Content creation without publish rights.
Section Access:
- Content
- Media
Content Permissions:
- Browse Node (F)
- Create (C)
- Update (A)
- Send to Publish (H)
Use Cases:
- Junior content creators
- Contractors
- Guest bloggers
- Require approval before publishing
Translators
Multi-language content management.
Section Access:
- Content (translation mode)
- Translation
Content Permissions:
- Browse Node (F)
- Update (A)
- Culture and Hostnames (I)
Use Cases:
- Translation agencies
- Multi-lingual content teams
- Localization specialists
Sensitive Data
Access to forms and member data.
Section Access:
- Members
- Forms
Content Permissions:
- Browse Node (F)
Use Cases:
Content Permission Letters
Each permission represented by a letter code:
| Code | Permission | Description |
|---|---|---|
| F | Browse Node | View content in tree |
| C | Create | Create new content |
| A | Update | Edit existing content |
| D | Delete | Delete content |
| U | Publish | Publish content |
| O | Copy | Duplicate content |
| M | Move | Move in content tree |
| S | Sort | Reorder child nodes |
| H | Send to Publish | Submit for approval |
| R | Permissions | Change permissions |
| Z | Audit Trail | View content history |
| I | Culture and Hostnames | Set culture/domains |
| P | Public Access | Set protection |
| N | Notifications | Manage notifications |
| K | Rollback | Restore previous versions |
Creating Custom User Groups
Via Backoffice
- Navigate to Users section
- Click User Groups folder
- Click Create User Group
- Enter details:
- Name: "Regional Editors"
- Alias:
regionalEditors(auto-generated) - Icon: Select appropriate icon
- Configure Section Access
- Set Default Permissions
- Set Start Nodes (optional)
- Click Save
Programmatically Create User Group
UserGroupService.cs:
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Services;
namespace YourProject.Services
{
public class CustomUserGroupService
{
private readonly IUserGroupService _userGroupService;
private readonly IContentService _contentService;
public CustomUserGroupService(
IUserGroupService userGroupService,
IContentService contentService)
{
_userGroupService = userGroupService;
_contentService = contentService;
}
public IUserGroup CreateRegionalEditorGroup(int startNodeId)
{
// Create user group
var userGroup = new UserGroup(0)
{
Name = "Regional Editors",
Alias = "regionalEditors",
Icon = "icon-users"
};
// Set section access
userGroup.AddAllowedSection("content");
userGroup.AddAllowedSection("media");
// Set default permissions
userGroup.Permissions = new[]
{
"F", // Browse
"C", // Create
"A", // Update
"U", // Publish
"O", // Copy
"M", // Move
"S" // Sort
};
// Set start node
if (startNodeId > 0)
{
var startNode = _contentService.GetById(startNodeId);
if (startNode != null)
{
userGroup.StartContentId = startNodeId;
}
}
// Save user group
_userGroupService.Save(userGroup);
return userGroup;
}
}
}
Advanced Permission Configuration
Set Node-Specific Permissions
Programmatically:
public void SetNodePermissions(int nodeId, int userId, string[] permissions)
{
var contentService = Services.ContentService;
var userService = Services.UserService;
var content = contentService.GetById(nodeId);
var user = userService.GetUserById(userId);
if (content != null && user != null)
{
var permissionSet = new EntityPermissionSet(
nodeId,
new EntityPermissionCollection(
permissions.Select(p => new EntityPermission(
user.Groups.First().Id,
nodeId,
new[] { p }
))
)
);
contentService.SetPermissions(permissionSet);
}
}
Usage:
// Give user only Update permission on specific node
SetNodePermissions(
nodeId: 1234,
userId: 5,
permissions: new[] { "A" } // Update only
);
Restrict by Document Type
Create document type filter:
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Notifications;
public class DocumentTypePermissionHandler :
INotificationHandler<ContentSavingNotification>
{
private readonly IUserService _userService;
public DocumentTypePermissionHandler(IUserService userService)
{
_userService = userService;
}
public void Handle(ContentSavingNotification notification)
{
var currentUser = _userService.GetUserById(notification.State["UserId"]);
foreach (var content in notification.SavedEntities)
{
// Restrict "Product" document type to Administrators only
if (content.ContentType.Alias == "product")
{
if (!currentUser.Groups.Any(g => g.Alias == "admin"))
{
notification.CancelOperation(
new EventMessage(
"Access Denied",
"Only Administrators can create Products",
EventMessageType.Error
)
);
}
}
}
}
}
Implement Custom Section
Create custom section for specific user group:
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Sections;
namespace YourProject.Sections
{
[Weight(40)]
public class ReportsSection : ISection
{
public string Alias => "reports";
public string Name => "Reports";
}
public class ReportsSectionComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder.Sections().Append<ReportsSection>();
}
}
}
Assign to user group:
var userGroup = _userGroupService.GetUserGroupByAlias("administrators");
userGroup.AddAllowedSection("reports");
_userGroupService.Save(userGroup);
Start Nodes (Content Tree Restrictions)
Set Start Nodes via Backoffice
- Navigate to Users section
- Click User Groups
- Select user group to edit
- Scroll to Start Nodes
- Click Choose under Content
- Select root node for this group
- Click Save
Example: Regional editors for North America only see /en/regions/north-america/ node and below.
Set Start Nodes Programmatically
public void SetStartNode(string userGroupAlias, int startNodeId)
{
var userGroup = _userGroupService.GetUserGroupByAlias(userGroupAlias);
if (userGroup != null)
{
userGroup.StartContentId = startNodeId;
userGroup.StartMediaId = null; // Full media access
_userGroupService.Save(userGroup);
}
}
Set Media Start Nodes
public void SetMediaStartNode(string userGroupAlias, int mediaFolderId)
{
var userGroup = _userGroupService.GetUserGroupByAlias(userGroupAlias);
if (userGroup != null)
{
userGroup.StartMediaId = mediaFolderId;
_userGroupService.Save(userGroup);
}
}
Permission Checking in Code
Check User Permissions
using Umbraco.Cms.Core.Services;
public class PermissionChecker
{
private readonly IUserService _userService;
private readonly IContentService _contentService;
public PermissionChecker(IUserService userService, IContentService contentService)
{
_userService = userService;
_contentService = contentService;
}
public bool UserCanPublish(int userId, int nodeId)
{
var user = _userService.GetUserById(userId);
var content = _contentService.GetById(nodeId);
if (user == null || content == null)
return false;
var permissions = _userService.GetPermissions(user, nodeId);
return permissions.Any(p => p.AssignedPermissions.Contains("U"));
}
public bool UserHasSection(int userId, string sectionAlias)
{
var user = _userService.GetUserById(userId);
return user?.Groups.Any(g => g.AllowedSections.Contains(sectionAlias)) ?? false;
}
}
Enforce Permissions in Controller
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Web.BackOffice.Controllers;
namespace YourProject.Controllers
{
[Authorize(Policy = "BackOfficeAccess")]
public class CustomApiController : UmbracoAuthorizedApiController
{
private readonly PermissionChecker _permissionChecker;
public CustomApiController(PermissionChecker permissionChecker)
{
_permissionChecker = permissionChecker;
}
[HttpPost]
public IActionResult PublishContent(int nodeId)
{
var currentUser = User.Identity.GetUserId<int>();
if (!_permissionChecker.UserCanPublish(currentUser, nodeId))
{
return Forbid("You do not have permission to publish this content");
}
// Publish content
var content = Services.ContentService.GetById(nodeId);
Services.ContentService.SaveAndPublish(content);
return Ok();
}
}
}
Workflow and Approval Process
Configure Send to Publish Workflow
For Writers (no publish rights):
- Writer creates/edits content
- Clicks Save
- Clicks Request Publish
- Notification sent to Editors
- Editor reviews and publishes
Notification Handler:
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Notifications;
public class SendToPublishNotificationHandler :
INotificationHandler<ContentSendToPublishNotification>
{
private readonly IEmailSender _emailSender;
private readonly IUserService _userService;
public SendToPublishNotificationHandler(
IEmailSender emailSender,
IUserService userService)
{
_emailSender = emailSender;
_userService = userService;
}
public async Task Handle(
ContentSendToPublishNotification notification,
CancellationToken cancellationToken)
{
var content = notification.Entity;
var sender = notification.State["User"];
// Get all Editors
var editors = _userService.GetAll(0, int.MaxValue, out long total)
.Where(u => u.Groups.Any(g => g.Alias == "editors"));
foreach (var editor in editors)
{
await _emailSender.SendAsync(new EmailMessage(
editor.Email,
"Content Awaiting Approval",
$"Content '{content.Name}' is awaiting your approval.\n\n" +
$"Submitted by: {sender}\n" +
$"View: {GetBackofficeUrl(content.Id)}",
isBodyHtml: false
), emailType: "ContentApproval");
}
}
}
Custom Permission Examples
Example: Regional Content Manager
Requirements:
- Access only to specific region (e.g., North America)
- Can create, edit, publish content in region
- Can upload media
- Cannot access Settings or Users
Implementation:
public void CreateRegionalManagerGroup(int regionNodeId)
{
var userGroup = new UserGroup(0)
{
Name = "North America Managers",
Alias = "naManagers",
Icon = "icon-globe"
};
// Sections
userGroup.AddAllowedSection("content");
userGroup.AddAllowedSection("media");
// Permissions
userGroup.Permissions = new[] { "F", "C", "A", "U", "O", "M", "S", "N" };
// Start node (North America region)
userGroup.StartContentId = regionNodeId;
_userGroupService.Save(userGroup);
}
Example: Form Administrator
Requirements:
- Access to Forms section only
- Can view/export form submissions
- No content access
Implementation:
public void CreateFormAdminGroup()
{
var userGroup = new UserGroup(0)
{
Name = "Form Administrators",
Alias = "formAdmins",
Icon = "icon-list"
};
// Only Forms section
userGroup.AddAllowedSection("forms");
// No content permissions needed
userGroup.Permissions = new string[] { };
_userGroupService.Save(userGroup);
}
Example: Read-Only Reviewer
Requirements:
- Can view all content
- Cannot create, edit, or publish
- Used for stakeholder reviews
Implementation:
public void CreateReviewerGroup()
{
var userGroup = new UserGroup(0)
{
Name = "Reviewers",
Alias = "reviewers",
Icon = "icon-eye"
};
userGroup.AddAllowedSection("content");
// Browse only
userGroup.Permissions = new[] { "F" };
_userGroupService.Save(userGroup);
}
Security Best Practices
Principle of Least Privilege
- Grant minimum access needed for role
- Review permissions quarterly
- Document permission changes
- Use specific user groups instead of broad access
Regular Audits
public class PermissionAuditService
{
public void AuditUserGroups()
{
var userGroups = _userGroupService.GetAll();
foreach (var group in userGroups)
{
Console.WriteLine($"Group: {group.Name}");
Console.WriteLine($" Sections: {string.Join(", ", group.AllowedSections)}");
Console.WriteLine($" Permissions: {string.Join(", ", group.Permissions)}");
Console.WriteLine($" Start Node: {group.StartContentId}");
var users = _userService.GetAll(0, int.MaxValue, out long total)
.Where(u => u.Groups.Any(g => g.Id == group.Id));
Console.WriteLine($" Users: {users.Count()}");
Console.WriteLine();
}
}
}
Separation of Duties
- Don't assign same user to conflicting groups
- Separate content creation from publishing (for sensitive content)
- Separate user management from content management
- Use workflow for high-value content
Common Issues
User Cannot See Content
Cause: Start nodes restrict visibility Solution: Check user group start nodes, adjust if needed
User Cannot Publish
Cause: Missing "Publish" (U) permission Solution: Add "U" permission to user group or use "Send to Publish" workflow
Changes Not Taking Effect
Cause: User needs to log out and back in Solution: Inform user to refresh session or restart browser
Next Steps
- Adding and Removing Users - User lifecycle management
- User Management Overview - Governance and policies
- Umbraco Security - Security best practices