Custom Roles
Role System
| Role | Source | Description |
|---|---|---|
owner | Built-in | Org creator, cannot be changed or transferred |
member | Built-in | Default role |
| Custom | Config file | Additional roles defined by developers |
Config File
Define roles in api/src/config/website/tenant.{suffix}.ts:
import type { TenantConfig } from "@readystart/api-core"
export const getTenantConfig = (): TenantConfig => ({
roles: [
{ value: "admin", label: "Admin" },
{ value: "editor", label: "Editor" },
{ value: "viewer", label: "Viewer" },
]
})
Rules
value: lowercase letters, numbers,_,-onlylabel: display name- Cannot use
ownerormember(auto-filtered) - Empty
valueorlabelare auto-filtered
Frontend Behavior
When Inviting
- Only
memberrole available → role selector is hidden - Custom roles exist → dropdown appears (excludes owner)
Member Management
- Action menu shows switchable roles (excludes current role and owner)
- Role column displays
label(e.g. “Admin”), notvalue
Backend Validation
Three layers of protection:
- Config loading: filters invalid values and reserved words
- validateRole: Controller-level check that role exists in config
- Service layer:
role === 'owner'fallback guard
// In Controller
private async validateRole(role: string) {
const roles = await getTenantRoles()
const allowed = roles.filter(r => r.value !== 'owner').map(r => r.value)
if (!allowed.includes(role)) {
throw new ForbiddenException(`Invalid role: ${role}`)
}
}
Usage in Business Code
// Check for a specific role
if (req.tenant?.role === 'admin' || req.tenant?.role === 'owner') {
// Has admin permissions
}
// Filter menu by role
{ index: "/reports", title: "Reports", icon: FileText, tenantRoles: ["owner", "admin"] }