Created by
Record who created, changed and deleted each row, in Postgres and in the repository.
The created-by block installs the track_actor trigger, which reads
auth.uid() inside the database, and a repository extension that fills
createdBy and updatedBy from the request's actor.
better-supabase add created-bygen marks every table that has the columns and installs the extension in
the generated schema. The actor comes from the connection:
const db = defineSupabase(schema).connect(supabase, {
actor: { id: user.id, kind: "user" },
});Rename the columns in the block's options (createdBy, updatedBy and
impersonatedBy, defaults created_by, updated_by and
impersonated_by).
What the extension writes
- Inserts get
createdByandupdatedBy. - Updates get
updatedBy. With trash, the delete is an update, soupdatedByrecords who deleted the row. - Upserts that may update only get
updatedBy. - Tables with an
impersonated_bycolumn getimpersonatedByon each insert and update made during impersonation: the admin fromactor.impersonator. The user's own writes leave it alone, so the row keeps the last impersonating admin. That is whattrack_actor(table, impersonated_by => 'impersonated_by')writes, and it also keepscreated_byon updates. - Anonymous requests set nothing.
createdBy,updatedByandimpersonatedByvalues you pass yourself are refused withinvalid_request, so a caller cannot sign a row as someone else. Pass{ override: true }for imports and backfills.
Server adapters fill actor from the verified JWT. Jobs run as the user who
enqueued them with bsb.forContext(job.context) (see
jobs), webhooks and scripts with
bsb.actingAs(userId), and work no user owns with bsb.admin(), whose actor
is service.
To read the id from somewhere else, install the extension yourself with a
resolve function; it replaces the one the schema installed:
import { createdByExtension } from "better-supabase/blocks/created-by";
defineSupabase(schema).use(
createdByExtension({ resolve: (context) => context.actor?.id }),
);For a tamper-proof trail, the trigger and the audit block record the database user, whatever the app sends.
Last updated on