must be owner of table messages
A migration that touches realtime.messages fails with 42501 must be owner of table messages. Why it happens, which statement to remove, and how to add Realtime policies safely.
What you see
A migration or SQL script fails partway through:
ERROR: 42501: must be owner of table messages
Policies you expected to exist on realtime.messages aren't there afterwards. A related message is permission denied for schema realtime when you try to create a table or function inside that schema.
Why it happens
realtime.messages is the table Realtime uses for Broadcast and Presence authorization. It belongs to Realtime's own admin role, not to postgres, and the realtime schema is locked down so your migrations can't break the service.
Row Level Security is already enabled on realtime.messages. A script that runs this line anyway:
alter table realtime.messages enable row level security;
fails, because Postgres checks that you own the table before it checks whether anything would change. The error aborts the transaction, so every statement after it, including your create policy lines, is rolled back too.
Creating and dropping policies on the table is allowed for postgres; it's only ownership-level changes (alter table, new tables or functions in realtime) that fail.
How to fix it
Delete the
alter table realtime.messages ...line from the migration. Nothing else needs to change.Keep only policy statements for this table. For example, a team chat where members can read and send on their team's topics:
create policy "Team members can read team topics" on realtime.messages for select to authenticated using ( exists ( select 1 from public.team_members m where m.user_id = (select auth.uid()) and 'team:' || m.team_id::text = (select realtime.topic()) ) ); create policy "Team members can send on team topics" on realtime.messages for insert to authenticated with check ( exists ( select 1 from public.team_members m where m.user_id = (select auth.uid()) and 'team:' || m.team_id::text = (select realtime.topic()) ) );If you need to confirm RLS is on, read the catalog instead of altering the table:
select relrowsecurity from pg_class where oid = 'realtime.messages'::regclass;Put your own helper functions in
public(or a private schema), not inrealtime.Re-run the migration. Since the failed one rolled back completely, it's safe to apply the fixed version.
On SupaHost
Migrations applied through the SupaHost MCP server's apply_migration tool run as a single transaction, so one failing line means none of the migration was applied, and it isn't recorded in the migration history. Fix the file and apply it again. The same is true if you paste the script into Studio's SQL editor as one batch.
If the migration came from a schema diff or dump of another project, check it for other statements on the realtime schema and remove them too; Realtime creates and manages that schema itself in each project database.