Skip to main content

Release Notes — April 10, 2026

This release completes the Custom Roles (RBAC) tier gate implementation and includes a database fix for a workspace roles index collision that prevented workspace roles from appearing in newly created workspaces.


Custom Roles — Enterprise Only (RBAC Tier Gate)

Overview

Creating, editing, and deleting custom permission roles is now gated behind the Enterprise tier. This is the industry standard: Clockify restricts custom roles to their Enterprise plan, Toggl Track restricts advanced roles to Premium+.

Default roles (Viewer, Member, Manager, Owner) remain available on all tiers with no change.

Backend

requireFeature('customRoles') guard on workspace role mutation endpoints:

EndpointGuard
POST /api/v1/workspace-rolesrequireFeature('customRoles')
PUT /api/v1/workspace-roles/:roleIdrequireFeature('customRoles')
DELETE /api/v1/workspace-roles/:roleIdrequireFeature('customRoles')

Non-Enterprise workspaces receive 403 FEATURE_NOT_AVAILABLE on any of these endpoints.

New FeatureName: customRoles added to the FeatureName union type in tier.types.ts.

FeatureGate.service.ts mapping: customRoles → { category: 'team', key: 'departments' }departments is true only on the Enterprise tier, making customRoles an Enterprise-only feature.

Frontend

useFeatureAccess.ts: customRoles mapped to 'enterprise' in both feature requirement maps.

UnifiedRolesPage.tsx: The New Role button is now wrapped in <FeatureGate feature="customRoles">. On non-Enterprise workspaces, the button is replaced by an UpgradePrompt component.

Pricing Comparison Page

/api/v1/pricing-comparison now returns a custom-roles row in the Team & Collaboration category:

FREEPROENTERPRISE
Custom roles

Fix: Workspace Roles Index Collision

Problem

Workspace roles (scope: workspace) have unitId: null because they are not tied to a specific workspace. The unique index { unitId, scope, name } caused an E11000 DuplicateKey error from the second workspace onwards, since all workspace roles shared unitId: null.

Effect: The /api/v1/workspace-roles?scope=workspace endpoint returned an empty list for any workspace beyond the first.

Fix

UnifiedRole.model.ts:

  • unitId field: required: false, default: null
  • Old single unique index removed
  • Two partial unique indexes added:
  • { unitId, scope, name } — only for scope: 'unit' entries
  • { workspaceId, scope, name } — only for scope: 'workspace' entries

roleCopy.service.ts:

  • copyWorkspaceRoles(): insertOnefindOneAndUpdate + upsert (idempotent)
  • copyWorkspacePermissions(): same pattern

Migration script fix-workspace-roles-index.ts (run once):

  • Dropped broken index on all 25 unit DBs
  • Created the two correct partial indexes
  • Backfilled missing workspace roles for all workspaces

Change Summary

LayerChange
Backend tier.types.tscustomRoles added to FeatureName union
Backend FeatureGate.service.tscustomRoles → team.departments in both feature maps
Backend workspaceRoles.routes.tsrequireFeature('customRoles') on POST / PUT / DELETE
Backend UnifiedRole.model.tsunitId: required: false + 2 partial unique indexes
Backend roleCopy.service.tsinsertOnefindOneAndUpdate + upsert (idempotent)
Backend pricing.controller.tscustom-roles row added to Team & Collaboration category
Backend stripe.config.ts'Custom roles (RBAC)' added to Enterprise features list
Frontend useFeatureAccess.tscustomRoles: 'enterprise' in both feature requirement maps
Frontend tier.types.tscustomRoles comment updated
Frontend UnifiedRolesPage.tsxNew Role button wrapped in <FeatureGate feature="customRoles">
Docs subscription-plans.mdCustom roles row added to Feature Comparison Matrix
Docs subscription-plans.mdCustom roles (RBAC) added to Enterprise features list
Docs workspace-roles.mdCustom Roles section updated: ENT-only notice + correct UI steps

Availability: Custom role create/edit/delete — Enterprise only. Default roles — all tiers.