Custom roles
The built-in system roles cover the most common access patterns, but every workspace can define its own roles that combine permissions in whatever way suits the organisation. Custom roles live inside a single workspace — Operentra is multi-tenant, so every role is scoped to the workspace (company) that created it. Roles you create are never visible to, or shared with, any other tenant. Only the platform operator, working from the separate /operator console, sits outside this workspace scope.
Managing roles requires the roles.manage permission (managing user accounts and their role assignments requires users.manage). Both are held by the Super Admin role and can be granted to any custom role.
Where roles are managed
Roles are managed from the admin console:
| Page | Route | Purpose |
|---|---|---|
| Roles | /admin/access/roles | List, create, duplicate, and delete roles |
| Role editor | /admin/access/roles/:id/permissions | Toggle a role's permissions |
| Permissions | /admin/access/permissions | Browse the full permission catalogue |
| Users | /admin/access/users | Assign roles to user accounts |
Everything is also available over the Access Control API — see API endpoints below.
Creating a custom role
From Roles (/admin/access/roles), click Create Role. A short dialog collects:
| Field | Required | Notes |
|---|---|---|
| Display Name | Yes | Human-friendly label, e.g. Payroll Manager |
| Slug | Yes | Lowercase identifier used in code and APIs; auto-derived from the display name and must be unique within the workspace |
| Description | No | Short summary of what the role can do |
Choosing Create & Configure creates the (initially empty) role and drops you straight into the role editor to pick its permissions.
Duplicating an existing role
Instead of starting from scratch, click Duplicate on any role card. This pre-fills the dialog with the source role's name and description and, on save, copies all of the source role's permissions into the new role. Duplicating a system role is the supported way to create an editable variant of it. After the copy is created you can open the new role and adjust its permissions.
The permission editor
The role editor (/admin/access/roles/:id/permissions) is a two-pane, module-scoped editor:
- Left rail — a searchable list of every permission module (Company, Employees, Attendance, Leave, Payroll, Salary Slips, Tax, Loans, Reports, Roles, Users, and so on). Each module shows a live
selected/totalbadge and a status dot: a filled check when every permission in the module is on, an amber ring when some are on, an empty circle when none are. - Right pane — the individual permissions for the currently selected module, each with a checkbox, its display name, its machine name (e.g.
payroll.view), and a description where one exists.
The search box filters both the module rail and the permission list, so typing part of a permission name jumps you to the relevant modules quickly.
Toggling permissions
- Per permission — tick or untick any checkbox in the right pane.
- Per module — use Select all in module / Deselect all in module in the module header to flip an entire feature area at once.
- Everything — the sidebar footer has Select all and Clear all buttons that act across every module.
The editor lets you rename the role (display name) and edit its description inline. The slug shown under the title is fixed once the role is created. A running summary at the bottom of the page reports how many permissions are selected across how many modules.
Changes are held locally until you press Save changes. An Unsaved badge and a browser "leave page?" prompt guard against losing edits; Reset discards them.
A role marked System cannot be modified or deleted. The editor opens it in view-only mode. To base a custom role on a system role, duplicate it first.
Assigning roles to users
Roles are assigned from Users (/admin/access/users). Open a user, edit their role assignments, and save. A user can hold multiple roles at once — their effective permissions are the union of every assigned role (see below).
The Access Control API deliberately uses a single management permission per area rather than separate view/create/update/delete permissions: users.manage covers all user operations and roles.manage covers all role operations.
How multiple roles combine
When a user holds several roles, their effective permission set is the union of all of them:
effectivePermissions = Role_A.permissions ∪ Role_B.permissions ∪ … ∪ Role_N.permissions
There is no "deny" permission. If any assigned role grants a capability, the user has it. This keeps authorisation simple and predictable.
| User | Roles | Effective result |
|---|---|---|
| A finance lead | Accountant + Department Head | All Accountant capabilities plus team attendance and leave approvals |
| A payroll specialist | HR Officer + a custom "Payroll Manager" | HR read access plus full payroll processing |
| A regular staff member | Employee | Self-service access only |
The permission check runs on the server for every request: the guard resolves the user's live permissions from the database each time, so a change to a role takes effect on the user's next request — there is no stale token to wait out. The web app caches the signed-in user's permissions (from GET /api/auth/me) to decide which menus and buttons to show, so the UI for an already-signed-in user only reflects a permission change after their session data is refreshed (a re-login or token refresh). Newly seeded permissions in particular require affected users to sign in again before the frontend surfaces them.
Editing and deleting roles
Editing — open any custom role from the Roles page (Edit) and adjust its display name, description, or permissions in the editor. System roles open as View only.
Deleting — the Delete action appears on a role card only when both conditions hold:
- the role is not a system role, and
- no users are currently assigned to it (its user count is zero).
Reassign any remaining users to another role first, then delete. The API enforces the same two rules and rejects a delete otherwise.
Worked examples
These examples describe the shape of a focused role. For the exact permission names available in each module, browse the in-app Permissions page or the Permission matrix.
A "Payroll Manager" typically needs read access to employees, the full Payroll and Salary Slips modules, the Tax module, selected Loans capabilities (approve/disburse), Reports, and the relevant Print permissions — but none of the HR administration or access-control permissions. Building this as one custom role is cleaner than assigning two broad system roles.
A "Recruiter" typically needs to create and update employee records, upload documents, create contracts, and read announcements and holidays — a narrow, low-risk footprint.
Start each role from the smallest set of permissions that does the job and add more only when a real need appears.
API endpoints
Roles and users are managed through the Access Control API under /api/access/*. Every route is company-scoped by the tenant guard and gated by the permission shown.
| Method | Path | Description | Permission |
|---|---|---|---|
| GET | /api/access/roles | List roles in the workspace (with permission and user counts) | roles.manage |
| GET | /api/access/roles/:id | Role detail with its permissions | roles.manage |
| POST | /api/access/roles | Create a custom role | roles.manage |
| PUT | /api/access/roles/:id | Update name, description, or permissions | roles.manage |
| DELETE | /api/access/roles/:id | Delete a custom role | roles.manage |
| GET | /api/access/permissions | Full permission catalogue, grouped by module | roles.manage |
| GET | /api/access/users | List user accounts | users.manage |
| POST | /api/access/users | Create a user | users.manage |
| PUT | /api/access/users/:id | Update a user and its role assignments | users.manage |
| DELETE | /api/access/users/:id | Delete a user | users.manage |
The API prefix defaults to api and is configurable via the API_PREFIX environment variable; paths above assume the default.
Both POST /api/access/roles and PUT /api/access/roles/:id accept an optional permissionIds array. On update, supplying permissionIds replaces the role's entire permission set with the list you send.
Best practices
- Least privilege — start narrow and add permissions as real needs surface, rather than starting broad and trimming.
- Name by function — "Payroll Manager", "Recruiter", not "Custom Role 1".
- Prefer duplicate-and-trim — clone the closest system role, then remove what you don't need.
- Review periodically — audit role assignments so users keep only the access they still require.
- Avoid role sprawl — if two roles differ by only a permission or two, consider consolidating.
Related
- Default roles — the system-seeded roles you can duplicate
- Permission matrix — the full catalogue of permissions per module