Audit Log
A minimal, first-party audit log for application-level events. It is a starter baseline you can keep, extend, or delete — not a compliance product. A simple time-based retention cron bounds table growth, but there is no legal hold, immutable storage, or SIEM export.
A minimal, first-party audit log for application-level events. It is a starter baseline you can keep, extend, or delete — not a compliance product. A simple time-based retention cron bounds table growth, but there is no legal hold, immutable storage, or SIEM export.
What it is
- A single Convex table,
audit_events, defined inpackages/backend/convex/schema.ts. - A typed write helper,
writeAuditEvent, inconvex/audit/events.ts. - A super-admin-only read query,
listAuditEvents, inconvex/audit/queries.ts. - A closed catalog of event types in
convex/audit/validators.ts.
Events are written inside the same transaction as the change they describe, so an audit row commits atomically with its action (no "best effort" gap for the shipped security/admin/billing/org paths).
Local code vs. the community component
This starter ships local code rather than the community convex-audit-log
component. The decision: a single table plus a small helper is easier to read,
trivial to grep, and easy for a buyer to delete than a third-party dependency
with its own schema and upgrade surface. Transparency wins for a boilerplate.
Re-evaluate if you need features it provides (e.g. retention tooling).
What is logged
See Audit Event Catalog for the full
catalog. Categories: auth, organization, member, billing, security,
admin, system.
Each row stores: the action, a derived category, a result
(success/failure/denied), a redacted actor snapshot (app user id, Better
Auth id, normalized email), an optional organization id, an optional target
(type + opaque id), a short human summary, optional flat metadata, and a
timestamp.
What is NOT logged
The write helper (redactAuditMetadata) drops sensitive keys and caps value
length, and call sites are written to avoid sensitive data in the first place:
- No passwords, password state, or hashes/salts.
- No verification codes, OTPs, session tokens, or session hashes.
- No magic-link tokens or raw Better Auth payloads.
- No provider webhook payloads, billing addresses, or card/CVV details.
- No raw request bodies. Metadata is a flat record of scalars only.
Metadata keys matching pass|secret|token|hash|salt|cookie|authorization|otp| code|credential|private|ssn|card|cvv are replaced with [redacted] even if a
caller passes them by mistake.
Reading the log
The log is visible only to platform super admins, at /admin/audit-log (see
Admin: Global Audit Log). The query requires
requireSuperAdmin and is indexed + paginated, so it stays fast at thousands of
rows. A Convex query cannot write, so denied reads are not themselves
audited; denied admin writes are (admin.access_denied).
Hardening paths (optional, not starter defaults)
- Convex Enterprise audit logging (
log.audit(...)) adds durable S3 delivery and request metadata (IP, user agent, request id). You can dual-write to it fromwriteAuditEventif you adopt Enterprise. See https://docs.convex.dev/production/integrations/audit-logging. - Convex log streams (Axiom/Datadog/PostHog/webhook, Pro plans) export deployment logs for external retention. See https://docs.convex.dev/production/integrations/log-streams.
Retention
The starter bounds audit_events growth with a daily cleanup cron,
audit.cleanup.deleteExpiredAuditEvents (registered in crons.ts), which
deletes events older than 365 days. It runs in bounded batches and
reschedules itself while a backlog remains.
This is a starter default, not a compliance policy. The window lives in
convex/audit/cleanup.ts (AUDIT_RETENTION_MS). Before launch:
- Change the window to match your needs, or remove the cron entirely to retain events forever.
- If you have legal-hold, immutable-storage, or export requirements, replace this with a dedicated solution (see the hardening paths above) — do not rely on this cron as a record-keeping guarantee.
Adding an event
See Recipe: Write an Audit Event.
Removing the feature
The audit log underpins the admin panel. To remove both, follow Removing the Admin Panel and Audit Log.
Rate Limiting
Server-side rate limiting on abuse-prone surfaces using the Convex rate-limiter component, configured in one place in rateLimit.ts.
Super Admin Access
Platform super admins can open the /admin panel. This is platform-scoped authorization, deliberately separate from organization/workspace roles: a workspace owner is not a platform admin.