Rowpass 42501: permission denied for table Not affiliated with Supabase

Rowpass / Guides

Grants vs row level security

Two checks run on every request. A grant comes first.

LayerQuestion it answersWhat a missing one looks like
GrantCan this role touch the table at all?42501, permission denied for table your_table
RLS policyWhich rows can this role see or change?Reads return no rows. Writes fail with 42501 and a row-level security message.

Row level security (RLS) is a Postgres feature that filters rows per request using rules called policies. A grant is a table-level permission for a role. You need both for every table the Data API exposes.

Things that surprise people

  • Adding policies does not create a grant. A table with perfect policies and no grant still returns 42501.
  • Adding policies does not remove a grant either. On older projects a table protected only by policies can still hand anon an insert path if the grant was never revoked.
  • With RLS on and no policy, a publishable (anon) key reads nothing.
  • Policies add up. A row is allowed if any one policy allows it, so an old using ( true ) policy defeats a newer strict one.
  • service_role bypasses RLS, and it still needs a grant.

Both layers, one migration

-- public.your_table: owner only
alter table public.your_table enable row level security;
revoke all on table public.your_table from anon, authenticated;
grant select, insert, update, delete on table public.your_table to authenticated;
grant select, insert, update, delete on table public.your_table to service_role;
drop policy if exists your_table_select_own on public.your_table;
create policy your_table_select_own on public.your_table
  for select to authenticated
  using ( (select auth.uid()) = user_id );
drop policy if exists your_table_insert_own on public.your_table;
create policy your_table_insert_own on public.your_table
  for insert to authenticated
  with check ( (select auth.uid()) = user_id );
drop policy if exists your_table_update_own on public.your_table;
create policy your_table_update_own on public.your_table
  for update to authenticated
  using ( (select auth.uid()) = user_id )
  with check ( (select auth.uid()) = user_id );
drop policy if exists your_table_delete_own on public.your_table;
create policy your_table_delete_own on public.your_table
  for delete to authenticated
  using ( (select auth.uid()) = user_id );

Write the fix for your tables

One table is free. The whole schema with a rollback file is $9 one-time, free during launch.

 

Waiting for a paste.

    Who can reach each table

    Your tables show up here, one row each, with a choice of who can reach them.

    The SQL appears here as soon as a table is found.

    Parsed in your browser. Your SQL is never uploaded, and no site or database is scanned.

    Questions

    Which do I fix first?

    The grant. Supabase's RLS guide says that when a request fails that your policy should allow, check the grants before you change the policy.

    Sources

    Related

    Did it fix your error?