Audit log of role changes β
Every time a role is created, a permission is toggled, or a user gets assigned to a different role or org unit, GCM writes a row to org_audit_log with the actor, the action, the affected entity, and the timestamp. The role catalogue is one of the most sensitive surfaces in the platform β granting members.delete to the wrong person can wipe years of history β so we treat its change log as a first-class feature, not a debug tool.

Where to find it β
Open Settings β Data Logs. The page shows every recent entry across your workspace, with filters for action type, actor email, and date range. To narrow to role-related events, type role into the Action filter β you'll see role.created, role.deleted, role_permission.toggled, user_role.assigned, user_role.revoked, user_unit.assigned, and user_unit.revoked. Each row links to the affected user or role so you can jump straight to the source record.
TIP
Data Logs requires config.manage. Administrators and any custom role that grants that key can open it; Leaders and Viewers can't. This is intentional β the audit trail is the last line of defence against an insider, so we don't want it visible to the same people whose actions it records.
What gets logged β
Every write touching the RBAC v2 tables emits an audit entry. The action key tells you what happened, the entity columns tell you who or what was affected, and the metadata JSONB carries the before/after detail.
| Action | When it fires | Metadata payload |
|---|---|---|
role.created | A new role is inserted into roles_v2 | name, key, description, is_system |
role.deleted | A custom role is removed | name, key, permissions_count |
role_permission.toggled | A switch in the permission matrix is flipped | role_id, permission_key, new_state (granted/revoked) |
user_role.assigned | A role is added to a user | user_id, role_id, role_name |
user_role.revoked | A role is removed from a user | user_id, role_id, role_name |
user_unit.assigned | A user is scoped to an org unit | user_id, org_unit_id, org_unit_name |
user_unit.revoked | A scope is cleared | user_id, org_unit_id, org_unit_name |
user.created | A new sign-in is provisioned | user_id, email, initial_roles |
user.suspended | The active toggle goes off | user_id, email |
user.reactivated | The active toggle goes back on | user_id, email |
user.password_reset | An admin force-resets a password | user_id, email |
Each entry also carries actor_id (the user who performed the action), actor_email (denormalised for fast searching even if the actor is later deleted), created_at (UTC, displayed in your org's timezone in the UI), and organization_id (which workspace the change belongs to).
How to read a row β
The default view shows: timestamp, actor, action, entity. Click any row to expand the metadata. The expanded view is the JSONB payload formatted as a key/value table β so a role_permission.toggled entry might show:
role_id: e7c3a... (Worship Leader)
permission_key: events.edit
new_state: grantedCombined with the timestamp and the actor email, that's enough to reconstruct exactly what changed. If you spot a grant you didn't expect, you can either revert it manually (open Roles & Permissions and flip the switch back) or contact the actor for context.
Retention β
Audit log entries are kept indefinitely for the lifetime of the workspace. They survive role deletions, user deletions, and even soft org deletes β the only thing that purges them is a hard workspace delete, which our team performs by signed request only.
This means your audit trail covers your entire history with the platform, which matters for compliance reviews and post-incident forensics. Don't worry about clutter; the data logs page paginates aggressively and the filter is fast.
Logging from edge functions β
Sensitive operations triggered by edge functions β for example, a workflow that auto-assigns members to a leader β also emit audit entries. The edge function calls log(ctx, action, entity) from _shared/caller-context.ts, which writes the row with the caller's identity, not the service role. So even automated changes show up under the user who triggered the workflow, with a metadata tag indicating it was workflow-driven.
This is why you'll occasionally see system entries in the actor column: they're the rare cron-driven cleanups (e.g. expiring an invitation token) where no human actor exists. Those are clearly tagged.
Common investigations β
A few patterns we walk customers through:
- "Who gave Sarah
members.delete?" β filter Action =role_permission.toggled, then search the metadata payload formembers.delete. The result shows every role that's ever been granted or revoked that key, who flipped it, and when. - "Why can John see the East Branch members?" β filter Action =
user_unit.assigned, search for John's user_id. The list shows every scope ever attached to him with timestamps. - "Did anyone delete a role this month?" β filter Action =
role.deleted, date range = current month. If the answer is yes, the metadata payload preserves the role's name and key so you can recreate it.
Exporting β
Click the Export button at the top right of Data Logs to download the current filtered view as CSV. The export includes all the columns you see plus the raw metadata JSONB, useful for forensic review in a spreadsheet or for handing off to an auditor.
Next β
- Recovering a locked-out user β when the audit log shows a mistake you need to undo.
- Granting permissions β what the toggles in the matrix correspond to.
- Platform admin audit log β the equivalent log for impersonation events run by the GCM team.