npx better-supabase sql add updated-atimport { defineConfig } from "better-supabase/config";
export default defineConfig({
sql: {
modules: {
// Modes: "managed". The default is "managed".
"updated-at": { mode: "managed" },
},
},
});npx better-supabase sql add updated-at writes these files into your declarative schema with the default layout. See SQL modules for the migration step and the layout options.
supabase/schemas/900_better_supabase_01_updated_at.sqlschema100 linessupabase/better-supabase-data/900_better_supabase_01_updated_at.sqldata12 lines
supabase/schemas/900_better_supabase_01_updated_at.sql
-- better-supabase module: updated-at
-- @bs-module updated-at@1 managed
-- Keeps an updated_at column current on every update.
-- Managed by `better-supabase sql add`; re-running it overwrites this file.
-- Change it through `sql.modules` in better-supabase.config.ts and the module's SQL hooks.
create schema if not exists better_supabase;
grant usage on schema better_supabase to anon, authenticated, service_role;
create or replace function better_supabase.set_updated_at()
returns trigger
language plpgsql
set search_path = ''
as $$
begin
-- The default column is assigned directly; jsonb_populate_record copies the
-- whole row, so it only serves other column names.
if tg_nargs = 0 or tg_argv[0] = 'updated_at' then
new.updated_at := now();
else
new := jsonb_populate_record(new, jsonb_build_object(tg_argv[0], now()));
end if;
return new;
end;
$$;
create or replace function better_supabase.replace_equivalent_triggers(
target regclass,
module_trigger text,
pattern text,
replace_trigger boolean
)
returns void
language plpgsql
set search_path = ''
as $$
declare
found record;
begin
for found in
select t.tgname as name, p.proname as fn
from pg_catalog.pg_trigger t
join pg_catalog.pg_proc p on p.oid = t.tgfoid
where t.tgrelid = replace_equivalent_triggers.target
and not t.tgisinternal
and t.tgname <> replace_equivalent_triggers.module_trigger
and p.proname ~* replace_equivalent_triggers.pattern
loop
if replace_trigger then
execute format('drop trigger %I on %s', found.name, target);
else
raise warning '% already has trigger % (%), which does what % does. Pass replace_trigger => true to drop it.',
target, found.name, found.fn, module_trigger;
end if;
end loop;
end;
$$;
revoke execute on function better_supabase.replace_equivalent_triggers(regclass, text, text, boolean) from public, anon, authenticated;
drop function if exists better_supabase.track_updated_at(regclass, text);
-- select better_supabase.track_updated_at('public.customers');
-- replace_trigger => true drops another trigger that sets updated_at (moddatetime, touch_*).
create or replace function better_supabase.track_updated_at(
target regclass,
column_name text default 'updated_at',
replace_trigger boolean default false
)
returns void
language plpgsql
set search_path = ''
as $$
begin
perform better_supabase.replace_equivalent_triggers(
target, 'bs_updated_at', 'updated_at|moddatetime|touch', replace_trigger
);
execute format('drop trigger if exists bs_updated_at on %s', target);
execute format(
'create trigger bs_updated_at before update on %s for each row execute function better_supabase.set_updated_at(%L)',
target,
column_name
);
end;
$$;
revoke execute on function better_supabase.track_updated_at(regclass, text, boolean) from public, anon, authenticated;
create schema if not exists better_supabase;
create table if not exists better_supabase.modules (
name text primary key,
version integer not null,
mode text not null,
installed_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
alter table better_supabase.modules enable row level security;
revoke all on better_supabase.modules from anon, authenticated;
grant select on better_supabase.modules to service_role;
supabase/better-supabase-data/900_better_supabase_01_updated_at.sql
-- better-supabase module: updated-at
-- @bs-module-data updated-at
-- Rows and settings a schema diff doesn't capture. Run `better-supabase sql data`
-- after the schema migration to put them in a migration.
-- Managed by `better-supabase sql add`; re-running it overwrites this file.
-- Change it through `sql.modules` in better-supabase.config.ts and the module's SQL hooks.
insert into better_supabase.modules (name, version, mode)
values ('updated-at', 1, 'managed')
on conflict (name) do update
set version = excluded.version, mode = excluded.mode, updated_at = now();
This block is SQL only: call its functions from your policies, RPCs and the other modules.
This module writes no outbox events.