A lightweight, trigger-based audit trail system for PostgreSQL. Automatically tracks all data changes (INSERT, UPDATE, DELETE) across your tables with full context — who changed what, when, and from where.
- Automatic change logging — captures every INSERT, UPDATE, and DELETE
- Data recovery — restore deleted rows or revert updates with
audit.restore() - Selective column exclusion — skip sensitive fields like
password_hash - Configurable events — choose which operations to track per table
- Custom primary keys — works with any primary key column name
- Error-safe — trigger errors are logged as warnings, never breaking your transactions
- Zero dependencies — pure PL/pgSQL, no extensions required
- PostgreSQL 13+
\i sql/deploy.sqlThis creates the audit schema with the log table, indexes, functions, and views.
Edit sql/setup.sql to set your database username and choose which tables to audit:
SET LOCAL audit.db_username = 'your_db_user';
SELECT audit.attach('public.orders', _events := '{INSERT,UPDATE,DELETE}');
SELECT audit.attach('public.users', _events := '{INSERT,UPDATE,DELETE}', _excluded := '{password_hash}');
SELECT audit.attach('public.products', _events := '{UPDATE,DELETE}');Then run it:
\i sql/setup.sql-- Track all operations
SELECT audit.attach('public.orders');
-- Track only UPDATE and DELETE, exclude sensitive columns
SELECT audit.attach('public.users',
_events := '{UPDATE,DELETE}',
_excluded := '{password_hash,api_token}'
);
-- Custom primary key column
SELECT audit.attach('public.legacy_table',
_primary_key := 'order_id'
);SELECT audit.detach('public.orders');-- Human-readable change log
SELECT * FROM audit.changes ORDER BY logged_at DESC LIMIT 20;
-- Filter by table
SELECT * FROM audit.changes
WHERE full_table_name = 'public.orders'
ORDER BY logged_at DESC;
-- Filter by record
SELECT * FROM audit.changes
WHERE full_table_name = 'public.users' AND pk_value = '42';-- Restore a deleted row or revert an update
SELECT * FROM audit.restore(7842);| restored_table | restored_action | restored_pk_value | status |
|---|---|---|---|
| public.orders | RE-INSERT | 123 | OK |
SELECT * FROM audit.info();The audit.log table stores all tracked changes:
| Column | Type | Description |
|---|---|---|
id |
BIGSERIAL | Primary key |
transaction_id |
BIGINT | PostgreSQL transaction ID |
schema_name |
TEXT | Source schema |
table_name |
TEXT | Source table |
pk_column |
TEXT | Primary key column name (default: id) |
pk_value |
TEXT | Primary key value of affected row |
operation |
TEXT | I = INSERT, U = UPDATE, D = DELETE |
logged_at |
TIMESTAMPTZ | Timestamp of the change |
username |
TEXT | Database session user |
client_address |
INET | Client IP address |
snapshot |
JSONB | Full row snapshot (before change for U/D) |
changed |
JSONB | Changed columns only (UPDATE) |
restored |
BOOLEAN | TRUE if reverted via audit.restore() |
.
├── sql/
│ ├── deploy.sql # Core audit infrastructure (schema, table, functions)
│ └── setup.sql # Permission grants and table attachment config
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
└── README.md