Must be owner of table messages.
Your migration ran an ALTER TABLE on realtime.messages, and PostgreSQL refuses that from anyone but the table's owner, which on Supabase is an internal role rather than postgres. Policy statements are allowed on that table; ALTER TABLE is not. Remove the alter, keep the policy, and the migration goes through.
What you are looking at.
ERROR: 42501: must be owner of table messages
What is this error? It is PostgreSQL's ordinary ownership check: only a table's owner may
alter it, and 42501 is its insufficient_privilege code. Proven on a real
PostgreSQL: a role that does not own the table gets exactly this message from
alter table realtime.messages enable row level security, and the same refusal from
any other ALTER TABLE on it. Supabase's own
troubleshooting article
names the owner: realtime.messages "is owned by an internal role,"
supabase_realtime_admin, of which postgres is not a member.
Two statements, two different doors.
On plain PostgreSQL even CREATE POLICY needs the owner; the suite confirms that a
non-owner is refused there too. Supabase changes that one thing. The
article
explains that supautils lets postgres run create policy,
alter policy and drop policy "on a fixed list of tables it does not own,
and" realtime.messages is on it; then: "the list covers policy statements only, so"
alter table is still refused. And the alter people reach for is unnecessary: in the
same article, "the first one is the common case. RLS is already enabled on"
realtime.messages.
Delete the alter. Keep the policy.
Remove every ALTER TABLE on realtime.messages from the migration
The line is usually alter table realtime.messages enable row level security;,
sometimes with an added column or a changed owner next to it. None of them can run as
postgres, and the migration runner rolls the whole file back on the first
error, so nothing after it was applied either.
Keep the policy, written against the channel topic
Realtime Authorization
reads the policies on this table "to generate access policies for your clients when they
connect to a channel topic." So realtime.topic() is the topic the client is
joining, and the policy admits or refuses that client for that topic. Proven on the
stand-in: with the topic set to room-1 a signed-in reader is admitted, and with
room-2 the same reader is refused everything. The policy below applies cleanly
when run by the table's owner and, on Supabase, when run by postgres.
create policy "signed-in users can read their room" on realtime.messages for select to authenticated using ( realtime.topic() = 'room-1' and extension = 'broadcast' );
Make the channel private, or the policy is never consulted
A public channel skips these policies. The same guide: "To enforce private channels you need to disable the" public-access setting in the project's Realtime settings, and subscribe with the private option in the client.
Straight answers.
- Is row level security off on realtime.messages?
- No. Supabase's troubleshooting article for this error says RLS is already enabled on the table, which is why the enable statement in the migration was never needed.
- Why can postgres create a policy on a table it does not own?
- Because the supautils extension on a Supabase project lets postgres run create, alter and drop policy on a fixed list of tables it does not own, and realtime.messages is on that list. On plain PostgreSQL the same statement is refused, which the suite behind this page confirms.
- Can I change the owner of realtime.messages to postgres?
- That is itself an ALTER TABLE, so it is refused for the same reason. Work through policies, which is the door Supabase left open on purpose.
One policy is rarely the only one. Check every table.
Our free auditor reads the policies on your own tables and lists the holes worst first, including the quiet ones that never raise an error. It runs in your own SQL editor in about a minute and never touches your rows.
Get the free RLS auditorAlso seeing 42501, new row violates row-level security policy on one of your own tables, or auth.uid() null on every request?
Migration still failing after this? Paste the error into the free diagnosis at rescue.ticassociation.com and get the cause and the fix path back in about a minute.