Supabase db push: must be owner of table (SQLSTATE 42501)
Why a migration that runs fine locally is refused on the remote project, which owner fixes work from the postgres role today, and when only Supabase support can help.
TL;DR#
supabase db push connects as the postgres role, and on a Supabase project that role is not a superuser. A migration that alters, drops or adds a constraint to a table owned by another role fails with must be owner of table <name> (SQLSTATE 42501), the whole migration file rolls back, and nothing is recorded. Find the table's owner. If you can act as that role, move the table to postgres and push again. If supabase_admin owns it, ask Supabase support. Granting privileges on the table does not help.
The error, as asked on Stack Overflow and reproduced on Supabase CLI 2.120.0:
Applying migration 20260113090000_add_display_name.sql...
ERROR: must be owner of table users (SQLSTATE 42501)
At statement: 0
alter table public.users add column display_name text
Try rerunning the command with --debug to troubleshoot the error.Why the remote project refuses a migration your local stack accepts#
The PostgreSQL ALTER TABLE reference is blunt: "You must own the table to use ALTER TABLE." The privileges chapter adds that this right "can be inherited by members of the owning role", and a superuser bypasses the check. On a hosted project the CLI and the dashboard both connect as postgres, which is not a superuser. Supabase's troubleshooting page on realtime.messages states that "The postgres role is not a member of it and is not a superuser", and that page also says the SQL editor and supabase db push "Both connect as postgres."
The 2022 question hit this because of where the table came from. In supabase/cli issue #128, a Supabase engineer explained that "Right now tables created using the table editor is owned by supabase_admin". Supabase then removed superuser access from the SQL editor and, according to the October 2022 security patch notice, reassigned the entities created through the SQL Editor to the postgres role. In 2023 the same issue was closed with the note that "all your tables and schemas are owned by postgres role" and "If that is not the case, please file a support ticket". Today's roles documentation states that "Queries you run in the Dashboard execute as postgres".
So in 2026 the error usually means one of three things:
- an old table kept a non-
postgresowner; - the table was created by another login role, such as an ORM user;
- the migration touches a schema that Supabase's internal roles own, such as
authorrealtime.
What fails, and what does not happen#
The failure is all-or-nothing per file. The same troubleshooting page states that "supabase db push runs each migration file in one transaction, so the file rolls back and nothing is written to supabase_migrations.schema_migrations." The reproduction below confirmed this. The migration's second statement (create table public.profiles …) did not run, and the history table stayed empty:
version | name
---------+------
(0 rows)You do not need supabase migration repair after this error. Fix the owner and run the same push again.
How this was reproduced#
The PostgreSQL part was reproduced locally on 11 October 2026 with Supabase CLI 2.120.0 (npm) against the official postgres:17 Docker image (PostgreSQL 17.11). The stand-in mimics a project's roles: postgres can log in but is not a superuser, and a superuser named supabase_admin owns public.users. Locally, supabase_admin is a superuser only because the image needs one. Supabase's documentation does not say whether that role is a superuser on hosted projects. The hosted behaviour (who the dashboard connects as, what support can change) comes from the Supabase documentation quoted above, not from this test. The local database has no TLS, so the test URL ends in ?sslmode=disable. A hosted connection string keeps TLS.
The migration used:
alter table public.users add column display_name text;
create table public.profiles (id bigint primary key references public.users (id), bio text);Fix: find the owner, then move the table to postgres#
-
Find who owns the table named in the error. Run this in the SQL editor or with
psqlon the project, replacinguserswith your table:sqlselect tableowner from pg_tables where schemaname = 'public' and tablename = 'users';In the reproduction, it returned
supabase_admin:texttableowner ---------------- supabase_admin (1 row)To list every table in
publicthatpostgresdoes not own, useselect tablename, tableowner from pg_tables where schemaname = 'public' and tableowner <> 'postgres';instead. -
If the owner is a login role you created, for example a Prisma or Drizzle user, have
postgresjoin that role and take over the table. The stand-in used a role calledapp_ownerand a table calledpublic.orders; substitute your own names.postgrescannot runALTER TABLE ... OWNER TOon its own. The other role cannot hand the table over either, because the PostgreSQL reference requires that "To alter the owner, you must be able to SET ROLE to the new owning role". In the stand-in,app_ownerwas refused withmust be able to SET ROLE "postgres". What worked, run aspostgres:sqlgrant app_owner to postgres; alter table public.orders owner to postgres;text$ psql -U postgres -c "grant app_owner to postgres;" GRANT ROLE $ psql -U postgres -c "alter table public.orders owner to postgres;" ALTER TABLEIn that test,
postgreshad createdapp_owner, on PostgreSQL 17. If the grant is refused on your project, connect as a role that is allowed to grant it. -
If the owner is
supabase_admin, you cannot change it from thepostgresrole. The reproduction shows it:text$ psql -U postgres -c "alter table public.users owner to postgres;" ERROR: must be owner of table usersThis is the case the CLI maintainers send to support ("please file a support ticket so that we can fix it for your project"). In the stand-in, the superuser ran the same statement, which gave the table to
postgres, and the push went through. -
Push again. With a linked project (
supabase link --project-ref your-project-refreplaces the 2022db remote set, see below), preview the migrations first and then apply them:bashnpx supabase db push --linked --dry-run npx supabase db push --linkedBoth flags are listed in the db push reference. For a database you are not linked to, pass
--db-urlwith a percent-encoded connection string instead of--linked. Against a self-hosted Postgres without TLS, CLI 2.120.0 fails the TLS handshake and prints the message below; add?sslmode=disableto that URL only, never to a hosted Supabase one:textThis server does not accept TLS. Set `sslmode=disable` on the connection string to connect in plaintext.
Check your connection string#
If you push with --db-url, paste the string here to check the port, host and password encoding before you run the migration. It runs in your browser, and nothing is sent.
Runs entirely in your browser — nothing you paste is sent anywhere, and the password is masked in every result. Still cautious? Replace the password with any string containing the same special characters: the diagnosis only needs its shape.
Verify the fix#
After the owner change, the same migration file applied cleanly:
Applying migration 20260113090000_add_display_name.sql...
Finished supabase db push.supabase migration list then shows the version on both sides, and a second push has nothing left to do:
Local | Remote | Time (UTC)
------------------|------------------|-----------------------
`20260113090000` | `20260113090000` | `2026-01-13 09:00:00` Remote database is up to date.One email a month — the latest explained errors
What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.
GRANT ALL is not ownership#
The first thing many people try is granting the postgres role every privilege on the table. In the reproduction, grant all privileges on table public.users to postgres; returned GRANT, and the next push failed with exactly the same must be owner of table users (SQLSTATE 42501). The PostgreSQL privileges chapter explains why: "The right to modify or destroy an object is inherent in being the object's owner, and cannot be granted or revoked in itself." ALTER TABLE, DROP TABLE and adding a foreign key constraint to the table (the case in issue #128) all need ownership.
Tables in auth and realtime: change the migration, not the owner#
Some tables are meant to stay with Supabase's own roles, and taking them over is not the fix.
realtime.messagesis owned bysupabase_realtime_admin. Supabase's troubleshooting page says thatpostgresmay create, alter and drop policies on it, but that anyalter tableis refused. Its fix is to "Remove theALTER TABLEline. Keep the policy". The usual offender isalter table realtime.messages enable row level security;, because RLS is already on.- The
authschema. The Auth 500-errors troubleshooting page expects objects there to be owned bysupabase_auth_admin. It treats a "must be owner of..." error as a sign that ORM migrations or manual changes damaged that ownership, and it points to Support whensupabase_adminowns those objects. The safer design is to keep your migrations on your ownpublictables, for example a profile table with a foreign key toauth.users, rather than alteringauth.usersitself.
When db push fails for a different reason#
Three other refusals show up while you clean up this error, and all three were reproduced with the same CLI.
You already ran the statement by hand. If you applied the change in the SQL editor while the push was failing, the next push fails on the objects that already exist:
ERROR: relation "profiles_bio_idx" already exists (SQLSTATE 42P07)Record the version as applied without running it, using migration repair, and push again:
npx supabase migration repair 20260113100000 --status applied --linked
npx supabase db push --linkedRepaired migration history: [20260113100000] => applied
Finished supabase migration repair.A local file is older than the newest remote migration. This happens when you rename or create files out of order while fixing things:
Found local migration files to be inserted before the last migration on remote database.
Rerun the command with --include-all flag to apply these migrations:
supabase/migrations/20260113085000_profiles_website.sqlRunning the push again with --include-all applied it. Do that only if the older file is still correct to run after the newer ones. The migration-workflow section of the zero-downtime Supabase migrations guide covers --dry-run before production, migration list --linked to inspect history, and using repair deliberately rather than deleting files.
The 2022 setup command no longer exists. The question configured the CLI with supabase db remote set. On CLI 2.120.0 that subcommand is gone:
Unknown subcommand "set" for "supabase db remote"Link the project instead with supabase link, or pass --db-url on each command.
Hand-made changes in the dashboard are what usually create these ownership and history gaps. The Next.js + Supabase production mistakes article argues for putting every schema change in a migration file. The same owner rule explains a dashboard-side cousin of this error, must be the owner of event trigger pgsodium_trg_mask_update. If your migrations come from Prisma rather than SQL files, the failure to look at first is the pooled port, described in Can't migrate schema using Prisma with Supabase. For supabase db push, check who owns the table before you rewrite the migration: when the owner is right, the same file applies unchanged.
Further reading: Fix: Supabase local logs "Backend error" (and old 502s).
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
Continue Reading
Fix: Supabase local logs "Backend error" (and old 502s)
The same local Studio Logs pipeline, broken three different ways since 2024: here is which one you are on, and the fix for each.
Supabase pgsodium_trg_mask_update: Fix Owner Error
Creating or editing a table in the Supabase dashboard throws a permission error that names a trigger you have never heard of. It is not a missing grant — it is Vault's pgsodium extension holding an event trigger your role does not own. Here is the workaround that keeps you working today and the fix that resets ownership for good.
Why Your Supabase Queries Are Slow (And How to Fix)
Slow Supabase queries kill your app feel and inflate your bill. Here are the six causes I keep seeing in production apps, and the exact SQL and code fixes for each one.


