Audit trails β
GCM keeps three separate audit trails so you can answer "who changed this?" without trawling through database backups. Each one lives in a different place because each one is asking a different question.

The three logs β
| Log | Lives at | Answers |
|---|---|---|
| Organization audit log | /activity-log and Data & Logs β Audit Log | Who in your org created, edited, or deleted a member, donation, group, ministry, school, attendance, or org unit? |
| Member activity log | A tab on every member's profile | What's happened to this person β joined a group, attended a meeting, received a workflow message, marked lost, recovered? |
| Import history | Data & Logs β Import History | Did the CSV import I ran last week finish, and how many rows failed? |
There's also a fourth log β the platform audit log β but it's reserved for Genius Church Manager platform admins (us) and tracks cross-tenant impersonation, plan changes, and superadmin role moves. You won't see it from inside your org.
Who can see audit data β
| Permission | Can see |
|---|---|
users.manage | The full /activity-log page across all entity types and actors |
config.manage | The Audit Log tab inside Data & Logs (same data, abbreviated view) |
| Any member's profile viewer | That member's own activity timeline |
| None of the above | Nothing β audit entries don't leak through other screens |
The activity log is permission-gated to users.manage on purpose: most staff don't need to see every teammate's edits, and exposing that data by default invites uncomfortable HR conversations.
What gets logged automatically β
A generic audit_log_writes() Postgres trigger watches eight entity tables and writes one row to org_audit_log per material change:
membersattendancesdonationsgroupsministriesorg_unitsschoolsschool_enrolments
Material change means: an INSERT, an UPDATE that touches at least one non-system column, or a soft delete (deleted_at set). Bulk updates from imports, workflow actions, and the API all flow through the same trigger β there is no way to write to those tables and bypass the log.
The trigger captures:
- Actor β the authenticated user's UUID and email (or
systemfor cron/webhook writes) - Action β
created,updated,deleted,restored, plus domain-specific verbs likemerge_members - Entity β type + ID, so the activity-log row can link directly to the affected member or group
- Metadata β for updates, the list of changed columns (useful for "what did you change?" but not the before/after values)
Member-specific events (joined a group, marked lost, received a workflow message) are written to a second table, member_activity_log, by application code rather than a trigger β that's the timeline you see on the member profile.
What is not logged β
Two deliberate omissions:
- Read access. Looking at a member doesn't write a row. If you need view-tracking for compliance, talk to us about the enterprise add-on.
- Before/after column values. Storing them would multiply log size by 10-50x. The "changed" array tells you which columns moved, not what they moved from and to. If you need point-in-time member state, use database backups instead.
Retention β
Audit rows are kept indefinitely on paid plans and pruned to the last 90 days on Starter. You can export the log before it ages out if you need long-term records β and you should, for any year you sent tax statements.
Where to go next β
- Organization audit log β the full cross-org "who did what" feed
- Member activity log β the per-member timeline
- Import history β bulk-import job status and row failures
- Exporting logs β CSV exports for compliance and backups
TIP
If you're investigating a specific change (a donation amount that looks wrong, a missing member), start at the organization audit log and filter by the entity. It's faster than scrolling the member's timeline.