Roles and permissions
Crow HR checks every API request on the server against one permission matrix before it reads any data. Hiding a button in the web app is cosmetic; the server decides. The table below is generated from that matrix (packages/core/src/access/matrix.ts) when the docs are built, so it is always the version the code enforces.
- Owner, HR admin and payroll admin are granted by an owner. One person can hold several.
- Manager applies automatically while the member’s employee record has at least one active direct report. Their team is everyone whose current reporting chain leads to them, at any depth, but not themselves.
- Employee applies automatically when the member is linked to an employee record.
A member’s rights are the union of their roles. See members and roles for how roles are granted.
The matrix
Section titled “The matrix”70 permissions in 32 areas. Cells: All = every record in the organisation, Team = the member's direct and indirect reports (not themselves), Own = their own records, Assigned = only items they are the assigned approver for, and a dash = denied.
| Permission | Owner | HR admin | Payroll admin | Manager | Employee |
|---|---|---|---|---|---|
| Security settings (sign-in methods, SSO, MFA policy, timeouts) | |||||
security_settings:read | All | — | — | — | — |
security_settings:update | All | — | — | — | — |
| Members and invitations | |||||
members:read | All | All | All | — | — |
members:createhr_admin may only invite members with no assigned roles. | All | All | — | — | — |
members:updatehr_admin may not grant or revoke roles. | All | All | — | — | — |
| Role grants | |||||
member_roles:grant | All | — | — | — | — |
| Company profile and structure | |||||
org_structure:read | All | All | All | All | All |
org_structure:create | All | All | — | — | — |
org_structure:update | All | All | — | — | — |
org_structure:archive | All | All | — | — | — |
| Employee directory | |||||
directory:read | All | All | All | All | All |
| Employment records and lifecycle | |||||
employment:read | All | All | All | Team | Own |
employment:create | All | All | — | — | — |
employment:update | All | All | — | — | — |
| Personal details | |||||
personal_details:read | All | All | All | — | Own |
personal_details:updateEmployees update via change request where required. | All | All | — | — | Own |
| Emergency contacts | |||||
emergency_contacts:read | All | All | — | Team | Own |
emergency_contacts:update | All | All | — | — | Own |
| Work passes | |||||
work_pass:read | All | All | All | — | Own |
work_pass:create | All | All | — | — | — |
work_pass:update | All | All | — | — | — |
| Compensation | |||||
compensation:read | All | — | All | — | Own |
compensation:createNobody changes their own compensation directly. | All | — | All | — | — |
compensation:updateNobody changes their own compensation directly. | All | — | All | — | — |
| Bank details | |||||
bank_details:read | All | — | All | — | Own |
bank_details:updateOwn bank details change only via change request. | All | — | All | — | Own |
| Payroll runs and statutory files | |||||
payroll:read | All | — | All | — | — |
payroll:create | All | — | All | — | — |
payroll:update | All | — | All | — | — |
payroll:finalise | All | — | All | — | — |
| Payslips and tax forms | |||||
payslips:readEmployees see released ones only. | All | — | All | — | Own |
| Leave | |||||
leave:read | All | All | All | Team | Own |
leave:createowner and hr_admin create on behalf of employees. | All | All | — | — | Own |
leave:updateNobody adjusts their own balance. | All | All | — | — | — |
| Government-paid leave report | |||||
leave_claims:read | All | All | All | — | — |
| Claims | |||||
claims:read | All | All | All | Team | Own |
claims:createowner and hr_admin create on behalf of employees. | All | All | — | — | Own |
claims:pay | All | — | All | — | — |
| Attendance, overtime and timesheets | |||||
attendance:read | All | All | All | Team | Own |
attendance:createowner and hr_admin create on behalf of employees. | All | All | — | — | Own |
attendance:updateNobody corrects their own timesheet. | All | All | — | Team | — |
| Approval tasks | |||||
approval_tasks:decide | Assigned | Assigned | Assigned | Assigned | Assigned |
approval_tasks:reassignhr_admin may not reassign payroll_run tasks. | All | All | — | — | — |
| Out-of-office cover for others | |||||
approval_delegations:manage | All | All | — | — | — |
| Email delivery status | |||||
notification_delivery:read | All | — | — | — | — |
| Rosters and shifts | |||||
rosters:readEmployees also see the published roster of their own location. | All | All | — | Team | Own |
rosters:create | All | All | — | Team | — |
rosters:update | All | All | — | Team | — |
rosters:publish | All | All | — | Team | — |
| Shift templates | |||||
shift_templates:update | All | All | — | — | — |
| Shift swaps and open-shift pickup | |||||
shift_swaps:createOnly for the employee's own published shifts. | — | — | — | — | Own |
| Documents | |||||
documents:readpayroll_admin: payroll-visible categories; manager: manager-visible categories, never medical; employee: employee-visible categories. | All | All | All | Team | Own |
documents:createEmployees upload to employee-uploadable categories only. | All | All | — | — | Own |
documents:update | All | All | — | — | — |
documents:delete | All | All | — | — | — |
| Approval policies, leave and claim types | |||||
policies:read | All | All | — | — | — |
policies:update | All | All | — | — | — |
| Claim types | |||||
claim_types:read | All | All | All | — | — |
| People reports | |||||
reports_people:readHeadcount and turnover. | All | All | — | — | — |
reports_people:export | All | All | — | — | — |
| Leave reports | |||||
reports_leave:read | All | All | — | Team | — |
reports_leave:export | All | All | — | Team | — |
| Attendance reports | |||||
reports_attendance:read | All | All | — | Team | — |
reports_attendance:export | All | All | — | Team | — |
| Claims reports | |||||
reports_claims:read | All | All | All | — | — |
reports_claims:export | All | All | All | — | — |
| Payroll cost reports | |||||
reports_payroll_cost:read | All | — | All | — | — |
reports_payroll_cost:export | All | — | All | — | — |
| Audit log | |||||
audit_log:readhr_admin: non-security, non-payroll events; payroll_admin: payroll, compensation and bank events. | All | All | All | — | — |
audit_log:export | All | All | All | — | — |
Rules on top of the matrix
Section titled “Rules on top of the matrix”- Records outside your scope look missing. A record of another organisation, or outside your team or own records, answers
404, so its existence is never revealed. A permission you do not have at all answers403. - Sensitive fields carry a class:
salary,nric,bankormedical. You receive a sensitive field only if the matrix gives you its resource (compensation, personal details, bank details).medicalis further limited to owners, HR admins, the employee and the approver assigned to that item. Fields you may not read are left out and listed inredactedFields. Exports apply the same rules. - NRIC/FIN and bank numbers are masked; revealing one needs a recent sign-in, is limited to 30 per hour and is audited.
- Segregation of duties: nobody changes their own compensation or bank details directly, whatever their role, and nobody decides their own approval request, except an owner when no one else is eligible (recorded as self-approved).
- Former members keep read-only access to their own released payslips and employee-visible documents.
- Install-level information (Settings → System) is visible to owners only.
Every resource in the matrix belongs to a built module and is enforced by the endpoints of that module.
Managers see leave status and employee numbers on rosters only for their reports. They can publish roster changes only for their reports; HR must publish changes affecting other teams. Children’s records require personal-details access; managers reviewing leave receive eligibility information without the child’s identity.