Supabase Audit Log Trigger: Track Every Row Change
Build a Supabase audit log trigger that no app code can bypass: OLD/NEW row capture as JSONB, auth.uid() actor tracking, tamper-proof RLS, and querying the trail.
Last updated: 2026-10-10 A Supabase audit log trigger is an AFTER INSERT OR UPDATE OR DELETE trigger that writes the old and new version of every changed row into an audit_logs table — and because it lives in Postgres, no application code, API client, or dashboard edit can bypass it. The short version: create the log table, write one plpgsql function that serializes OLD and NEW to JSONB, attach it to your tables. Ten minutes of SQL buys you a tamper-proof history of who changed what and when, plus the ability to revert any row to a previous state. The rest of this guide builds the full pattern: actor capture with auth.uid() , append-only enforcement, RLS, and querying the trail. New to the stack? Start with our complete Flutter and Supabase guide . If you have not built triggers on Supabase before, our Supabase database triggers guide covers the fundamentals this article builds on. What you'll build An audit_logs table storing table name, record id, action, old/new row snapshots, actor, and timestamp One generic trigger function that captures OLD and NEW rows as JSONB for any table Actor tracking via auth.uid() so every entry records who made the change A tamper-proof setup: the trigger is the only writer, and the log is append-only RLS policies so only admins can read the trail Queries for row history and one-statement reverts, plus a retention job Trigger vs pgAudit vs app-level logging Three approaches compete for the "who changed my data" job. Pick the wrong one and you get either a log your app can silently skip or an extension that answers a different question. Approach What it records Bypasses Best for Trigger-based audit log Row-level before/after snapshots ( OLD / NEW as JSONB) plus actor Nothing — fires inside Postgres on every write Compliance, revert…