From 2415776436927dedb360dae082d03caee607891a Mon Sep 17 00:00:00 2001 From: Chris Gwilliams <517923+encima@users.noreply.github.com> Date: Fri, 7 Nov 2025 20:24:55 +0200 Subject: [PATCH] feat: add docs on creating event triggers (#40239) --- .../NavigationMenu.constants.ts | 4 + .../database/postgres/event-triggers.mdx | 135 ++++++++++++++++++ .../database/postgres/roles-superuser.mdx | 2 - 3 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 apps/docs/content/guides/database/postgres/event-triggers.mdx diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index e40c690b7e9..a053d8a96a2 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -1045,6 +1045,10 @@ export const database: NavMenuConstant = { name: 'Managing connections', url: '/guides/database/connection-management' as `/${string}`, }, + { + name: 'Managing event triggers', + url: '/guides/database/postgres/event-triggers' as `/${string}`, + }, ], }, { diff --git a/apps/docs/content/guides/database/postgres/event-triggers.mdx b/apps/docs/content/guides/database/postgres/event-triggers.mdx new file mode 100644 index 00000000000..0254f74757e --- /dev/null +++ b/apps/docs/content/guides/database/postgres/event-triggers.mdx @@ -0,0 +1,135 @@ +--- +id: 'postgres-event-triggers' +title: 'Event Triggers' +description: 'Automatically execute SQL on database events.' +subtitle: 'Automatically execute SQL on database events.' +--- + +In Postgres, an [event trigger](https://www.postgresql.org/docs/current/event-triggers.html) is similar to a [trigger](/docs/guides/database/postgres/triggers), except that it is triggered by database level events (and is usually reserved for [superusers](/docs/guides/database/postgres/roles-superuser)) + +With our `Supautils` extension (installed automatically for all Supabase projects), the `postgres` user has the ability to create and manage event triggers. + +Some use cases for event triggers are: + +- Capturing Data Definition Language (DDL) changes - these are changes to your database schema (though the [pgAudit](/docs/guides/database/extensions/pgaudit) extension provides a more complete solution) +- Enforcing/monitoring/preventing actions - such as preventing tables from being dropped in Production or enforcing RLS on all new tables + +The guide covers two example event triggers: + +1. Preventing accidental dropping of a table +2. Automatically enabling Row Level Security on new tables in the `public` schema + +## Creating an event trigger + +Only the `postgres` user can create event triggers, so make sure you are authenticated as them. As with triggers, event triggers consist of 2 parts + +1. A [Function](/docs/guides/database/functions) which will be executed when the triggering event occurs +2. The actual Event Trigger object, with parameters around when the trigger should be run + +### Example trigger function - prevent dropping tables + +This example protects any table from being dropped. You can override it by temporarily disabling the event trigger: `ALTER EVENT TRIGGER dont_drop_trigger DISABLE;` + +```sql +-- Function +CREATE OR REPLACE FUNCTION dont_drop_function() + RETURNS event_trigger LANGUAGE plpgsql AS $$ +DECLARE + obj record; + tbl_name text; +BEGIN + FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects() + LOOP + IF obj.object_type = 'table' THEN + RAISE EXCEPTION 'ERROR: All tables in this schema are protected and cannot be dropped'; + END IF; + END LOOP; +END; +$$; + +-- Event trigger +CREATE EVENT TRIGGER dont_drop_trigger +ON sql_drop +EXECUTE FUNCTION dont_drop_function(); +``` + +### Example trigger function - auto enable Row Level Security + +```sql +CREATE OR REPLACE FUNCTION rls_auto_enable() +RETURNS EVENT_TRIGGER +LANGUAGE plpgsql +SECURITY DEFINER +SET search_path = pg_catalog +AS $$ +DECLARE + cmd record; +BEGIN + FOR cmd IN + SELECT * + FROM pg_event_trigger_ddl_commands() + WHERE command_tag IN ('CREATE TABLE', 'CREATE TABLE AS', 'SELECT INTO') + AND object_type IN ('table','partitioned table') + LOOP + IF cmd.schema_name IS NOT NULL AND cmd.schema_name IN ('public') AND cmd.schema_name NOT IN ('pg_catalog','information_schema') AND cmd.schema_name NOT LIKE 'pg_toast%' AND cmd.schema_name NOT LIKE 'pg_temp%' THEN + BEGIN + EXECUTE format('alter table if exists %s enable row level security', cmd.object_identity); + RAISE LOG 'rls_auto_enable: enabled RLS on %', cmd.object_identity; + EXCEPTION + WHEN OTHERS THEN + RAISE LOG 'rls_auto_enable: failed to enable RLS on %', cmd.object_identity; + END; + ELSE + RAISE LOG 'rls_auto_enable: skip % (either system schema or not in enforced list: %.)', cmd.object_identity, cmd.schema_name; + END IF; + END LOOP; +END; +$$; + +DROP EVENT TRIGGER IF EXISTS ensure_rls; +CREATE EVENT TRIGGER ensure_rls +ON ddl_command_end +WHEN TAG IN ('CREATE TABLE', 'CREATE TABLE AS', 'SELECT INTO') +EXECUTE FUNCTION rls_auto_enable(); +``` + +### Event trigger Functions and firing events + +Event triggers can be triggered on: + +- `ddl_command_start` - occurs just before a DDL command for almost all objects within a schema +- `ddl_command_end` - occurs just after a DDL command for almost all objects within a schema +- `sql_drop` - occurs just before `ddl_command_end` for any DDL commands that `DROP` a database object (note that altering a table can cause it to be dropped) +- `table_rewrite` - occurs just before a table is rewritten using the `ALTER TABLE` command + + + +Event triggers run for each DDL command specified above and can consume resources which may cause performance issues if not used carefully. + + + +Within each event trigger, helper functions exist to view the objects being modified or the command being run. For example, our example calls `pg_event_trigger_dropped_objects()` to view the object(s) being dropped. For a more comprehensive overview of these functions, read the [official event trigger definition documentation](https://www.postgresql.org/docs/current/event-trigger-definition.html) + +To view the matrix commands that cause an event trigger to fire, read the [official event trigger matrix documentation](https://www.postgresql.org/docs/current/event-trigger-matrix.html) + +## Disabling an event trigger + +You can disable an event trigger using the `alter event trigger` command: + +```sql +ALTER EVENT TRIGGER dont_drop_trigger DISABLE; +``` + +## Dropping an event trigger + +You can delete a trigger using the `drop event trigger` command: + +```sql +DROP EVENT TRIGGER dont_drop_trigger; +``` + +## Resources + +- Official Postgres Docs: [Event Trigger Behaviours](https://www.postgresql.org/docs/current/event-trigger-definition.html) +- Official Postgres Docs: [Event Trigger Firing Matrix](https://www.postgresql.org/docs/current/event-trigger-matrix.html) +- Supabase blog: [Postgres Event Triggers without superuser access](/blog/event-triggers-wo-superuser) diff --git a/apps/docs/content/guides/database/postgres/roles-superuser.mdx b/apps/docs/content/guides/database/postgres/roles-superuser.mdx index 552ce4b3ba0..0f34e691799 100644 --- a/apps/docs/content/guides/database/postgres/roles-superuser.mdx +++ b/apps/docs/content/guides/database/postgres/roles-superuser.mdx @@ -12,7 +12,5 @@ However, this does mean that some operations, that typically require `superuser` ## Unsupported operations -- `CREATE SUBSCRIPTION` -- `CREATE EVENT TRIGGER` - `COPY ... FROM PROGRAM` - `ALTER USER ... WITH SUPERUSER`