Audit Trail
The audit trail module records security-relevant events across the application. Auth events are captured automatically through a BetterAuth plugin, while application-level events can be logged explicitly from any Convex function.
Event sources
Section titled “Event sources”Auth events (automatic)
Section titled “Auth events (automatic)”The auditTrail BetterAuth plugin hooks into 30+ auth lifecycle events, including:
- Sign-in, sign-up, and sign-out (email, social, passkey)
- Password changes and resets
- Email verification
- 2FA enable, disable, and verify
- Passkey registration and deletion
- Session revocation
- Social account linking
- Admin actions (ban, impersonate, role changes)
- Organization operations (create, update, delete, member management)
Application events (explicit)
Section titled “Application events (explicit)”Any module can log audit events by scheduling the internal log function:
import { internal } from "../../../_generated/api"import { audit } from "@red/backend-contract/core/audit"
// Inside an admin or authenticated mutationawait ctx.scheduler.runAfter( 0, internal.modules.core.audit.audit_internal.log, audit(ctx.user, "myModule.api", { eventName: "resource.created", eventType: "mutation", readOnly: false, targetType: "book", targetId: resourceId, status: "success" }))Querying logs
Section titled “Querying logs”Admin view (all logs)
Section titled “Admin view (all logs)”Global admins can query the full audit log with filters:
import { api } from "@red/backend/api"
const logs = await convex.query( api.modules.core.audit.audit_api.listAuditLogs, { paginationOpts: { numItems: 50, cursor: null }, actorId: "user-id", // optional eventName: "user.login", // optional status: "success", // optional orgId: "org-id", // optional startDate: 1710000000000, // optional endDate: 1710100000000 // optional })Organization view (scoped)
Section titled “Organization view (scoped)”Authenticated users can query logs scoped to their active organization:
const logs = await convex.query( api.modules.core.audit.audit_api.listOrgAuditLogs, { paginationOpts: { numItems: 50, cursor: null } })This endpoint requires the ac:read permission and automatically filters by the user’s active organization.
Log schema
Section titled “Log schema”Each audit log entry captures:
| Field | Type | Description |
|---|---|---|
actorId | string | User ID or apikey:{keyId} |
actorRole | string? | Global role at time of action |
eventName | string | Event identifier (e.g. user.login, organization.create) |
eventType | "auth" | "mutation" | "query" | Event category |
readOnly | boolean | Whether the action modified data |
source | string | Module or plugin that logged the event |
targetType | string? | Resource type affected |
targetId | string? | Resource ID affected |
orgId | string? | Organization context |
ipAddress | string? | Client IP address |
userAgent | string? | Client user agent |
request | object? | Additional metadata |
status | "success" | "error" | Outcome |
errorMessage | string? | Error details on failure |
Indexes: by_actor, by_event, by_status, by_org.
Key files
Section titled “Key files”| File | Purpose |
|---|---|
packages/backend/src/convex/lib/core/auth/audit_trail.ts | BetterAuth plugin (auto-logs auth events) |
packages/backend/src/convex/modules/core/audit/audit_api.ts | Admin and org-scoped query endpoints |
packages/backend/src/convex/modules/core/audit/audit_internal.ts | Internal log mutation |
packages/backend/src/convex/modules/core/audit/audit_table.ts | Database schema |
packages/backend-contract/src/core/audit.ts | Shared audit() helper and validators |