Files
Miranda LimonczenkoandClaude Opus 5 6368f00ca0 docs(database): restructure the RLS guide by information type (#49017)
## Problem

The guide alternated between context, procedure, and reference on almost
every heading. A reader who wanted to write a policy passed through four
context or reference sections to reach one. A reader who wanted the
model had to skip three procedures.

## Solution

- Group into three sections by information type: `Understand Row Level
Security`, `Secure a table with RLS`, and `RLS reference`, with a
navigation intro.
- Merge the four policy sections. They repeated the same setup block,
burying the clause that differed. One setup block now precedes four
short policy examples.
- Move the auto-enable recipe into `event-triggers.mdx`, whose stub
section's entire body was a link back here.
- Relocate the stranded `auth.uid()` caution into the `auth.uid()`
reference.
- Lift the revoke-and-grant procedure out of the danger admonition and
merge it with the two other places that taught `enable row level
security`.
- Point the Grafana IO chart entry at the performance guide. Its
`#rls-performance-recommendations` anchor went away when tuning split
out in #49016.

765 lines to 582. 30 headings to 25.

Headings are demoted rather than renamed wherever anything links to
them. Every inbound anchor in the repo still resolves; the only one
removed, `#auto-enable-rls-for-new-tables`, was referenced solely by the
`event-triggers.mdx` stub this PR replaces.

## Note on the history

Rebuilt from `master` after #49011, #49015, and #49016 merged. The
branch previously carried those 10 commits plus rebase churn against
them.

Rebasing naively would have reverted review feedback from #49016
(`70fa812`), which removed the benchmarks table and the "This guide"
opener from the performance guide. Those are deliberately not restored
here. The only changes to that file are two missing `await`s and a join
predicate that was a tautology while unqualified.

The three PRs stacked on this one (#49268, #49269, #49270) have been
rebased onto the new base.

## Manual testing

1. Open the [Row Level Security
guide](https://docs-git-docs-rls-restructure-supabase.vercel.app/docs/guides/database/postgres/row-level-security)
on the preview. Three top-level sections appear in the table of
contents.
2. Select each link in the intro. All three jump to their section.
3. Open [Event
triggers](https://docs-git-docs-rls-restructure-supabase.vercel.app/docs/guides/database/postgres/event-triggers).
The auto-enable section holds the full recipe instead of a link.
4. Open the [performance
guide](https://docs-git-docs-rls-restructure-supabase.vercel.app/docs/guides/database/postgres/row-level-security-performance).
No benchmarks table, and the three bullets at the top link into the RLS
guide.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Documentation**
* Reworked the Row Level Security guide with clearer guidance on grants,
policies, permissions, performance, testing, views, and secure
functions.
* Added a complete example for automatically enabling RLS on newly
created public tables.
* Improved SQL examples and clarified table references in RLS
performance guidance.
* Corrected grammar in the Grafana chart troubleshooting documentation.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 14:51:06 -07:00

141 lines
5.9 KiB
Plaintext

---
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
If you want [Row Level Security](/docs/guides/database/postgres/row-level-security) enabled automatically for new tables, create an event trigger that runs after table creation and calls `ALTER TABLE ... ENABLE ROW LEVEL SECURITY` on each newly created table.
```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;
RAISE;
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();
```
Note that this applies to tables created after the trigger is installed. Existing tables still need RLS enabled manually.
### Event trigger Functions and firing events
Event triggers can be triggered on:
- `ddl_command_start` - occurs before a DDL command for almost all objects within a schema
- `ddl_command_end` - occurs after a DDL command for almost all objects within a schema
- `sql_drop` - occurs 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 before a table is rewritten using the `ALTER TABLE` command
<Admonition type="caution">
Event triggers run for each DDL command specified above and can consume resources which may cause performance issues if not used carefully.
</Admonition>
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/17/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/17/event-trigger-matrix.html)
- Supabase blog: [Postgres Event Triggers without superuser access](/blog/event-triggers-wo-superuser)