Supabase 42501: permission denied for table — how to fix it
By StackSiren · Published
Postgres error 42501 on a Supabase table means the role your request runs as (anon, authenticated, or another API role) does not have a GRANT for that operation. Grant only the privileges each role needs, enable RLS, and add policies for rows — do not "fix" it by granting everything to anon or by putting a service_role key in the browser.
Clients often surface this as HTTP 401/403 from the Data API. Supabase's 42501 troubleshooting doc lists the usual causes: missing table grants, custom schemas, column-level restrictions, forbidden schemas, and some RLS write failures. From 30 October 2026, missing grants on new public tables become much more common because Supabase stops auto-exposing them — see Supabase stops auto-exposing new tables on 30 October 2026.
What 42501 means
In Postgres, 42501 is insufficient_privilege. For the Supabase Data API (PostgREST / supabase-js), a typical message looks like:
permission denied for table orders
or a client wrapper around that SQLSTATE. The request reached Postgres; the role was not allowed to do that command on that object.
Quick triage:
| Symptom | Likely cause |
|---|---|
New table, works in SQL editor, fails from supabase-js |
No GRANT to anon / authenticated |
| Fails only when signed out | Missing anon grant or RLS/policy for anon |
Fails on select * but some columns work |
Column-level REVOKE / column privileges |
Touching auth / vault from the client |
Intentionally forbidden schemas |
| Insert/update fails with 42501 after grants look fine | RLS / WITH CHECK rejecting the write (see Supabase's note on RLS and 42501) |
Grants vs RLS (both required)
These are separate layers. Confusing them is how people end up with either a locked app or a wide-open one.
| Layer | Question it answers | Typical SQL |
|---|---|---|
| GRANT | May this role touch the table/columns at all? | grant select on public.orders to authenticated; |
| RLS + policies | Which rows (and which write shapes) are allowed? | create policy … using ((select auth.uid()) = user_id) |
- Without a grant, the Data API never gets as far as useful row filtering — you get
42501. - With grants but RLS enabled and no policy, client roles see zero rows (or writes fail). That is the safe default, not something to bypass with
using (true)— see What does USING (true) mean?. service_role/ secret keys bypass RLS; they still need grants, and they must never ship to the browser (service_role exposed).
Full walkthrough for builders: Lovable Supabase RLS and Bolt Supabase RLS checklist.
Step 1: Confirm who is calling and what they have
In the SQL editor:
-- Privileges currently granted on the table
select grantee, privilege_type
from information_schema.role_table_grants
where table_schema = 'public'
and table_name = 'your_table' -- change me
order by grantee, privilege_type;
-- Is RLS on? How many policies?
select c.relname,
c.relrowsecurity as rls_enabled,
(select count(*) from pg_policies p
where p.schemaname = 'public' and p.tablename = c.relname) as policies
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public' and c.relname = 'your_table';
Reproduce with curl using only keys you own:
# Signed-out path (anon / publishable key)
curl -s -i "https://YOUR-PROJECT.supabase.co/rest/v1/your_table?select=*&limit=1" \
-H "apikey: YOUR_PUBLISHABLE_OR_ANON_KEY" \
-H "Authorization: Bearer YOUR_PUBLISHABLE_OR_ANON_KEY"
If the JSON/error body mentions permission denied / 42501, you are in grant (or column-privilege) territory. Our Supabase RLS checker exercises the signed-out Data API path on sites you own; pair it with the SQL above when you are fixing schema.
Step 2: The safe grant (minimum privileges)
Treat create-table + grant + RLS + policies as one migration. Example for a private, per-user table that signed-out visitors should not reach:
-- 1) Table (sketch)
create table public.notes (
id uuid primary key default gen_random_uuid(),
user_id uuid not null references auth.users (id),
body text not null,
created_at timestamptz not null default now()
);
-- 2) Grants: only what each role needs
-- No anon grant → signed-out clients cannot use the Data API on this table
grant select, insert, update, delete on table public.notes to authenticated;
grant select, insert, update, delete on table public.notes to service_role;
-- 3) RLS
alter table public.notes enable row level security;
-- 4) Policies (owner-scoped)
create policy "notes_select_own" on public.notes
for select to authenticated
using ((select auth.uid()) = user_id);
create policy "notes_insert_own" on public.notes
for insert to authenticated
with check ((select auth.uid()) = user_id);
create policy "notes_update_own" on public.notes
for update to authenticated
using ((select auth.uid()) = user_id)
with check ((select auth.uid()) = user_id);
create policy "notes_delete_own" on public.notes
for delete to authenticated
using ((select auth.uid()) = user_id);
For a public read catalogue (everyone can read, only signed-in users write):
grant select on table public.products to anon, authenticated;
grant insert, update, delete on table public.products to authenticated;
grant select, insert, update, delete on table public.products to service_role;
alter table public.products enable row level security;
create policy "products_public_read" on public.products
for select to anon, authenticated
using (true); -- intentional public read; keep writes locked down
create policy "products_auth_write" on public.products
for insert to authenticated
with check ((select auth.uid()) = seller_id);
-- …update/delete similarly
A bare using (true) on select for a catalogue can be intentional. The same pattern on a private notes table is not — see the USING (true) post linked above.
If you use sequences / identity columns, grant usage on those sequences to the same roles that insert, or inserts fail for a related privilege error.
Step 3: Don't grant everything to anon
When an AI builder or a quick dashboard click "fixes" 42501, the dangerous patterns are:
-- Too broad: every future operation for signed-out visitors
grant select, insert, update, delete on all tables in schema public to anon;
-- Or a single sensitive table opened completely
grant all on table public.users to anon;
That makes the Data API succeed — and makes RLS the only remaining guard. If RLS is off, missing, or using (true) for writes, anyone with your publishable key can read or change data.
Safer rules of thumb:
- Default deny for
anon. Addanongrants only for data that is meant to be public (or for carefully limited inserts such as a contact form with select revoked). - Prefer per-table, per-privilege grants in the same migration as the table.
- Never put
service_role/sb_secret_in client code to silence 42501. That bypasses RLS entirely. - Update your builder prompt so new tables always get grants + RLS + policies together (template in the 30 October grant-change post).
Step 4: Other 42501 causes (quick fixes)
From Supabase's troubleshooting guide:
- Custom schema: grant
usageon the schema and appropriate table privileges toanon/authenticated/service_role, and expose the schema in Data API settings if required. - Column privileges:
select *expands to columns the role cannot read. Grant the columns you need, or select an allowed column list. - Forbidden schemas: don't query
auth/vaultfrom the client; use security-definer RPCs only when you accept that design. - RLS write failures reported as 42501: fix
WITH CHECK/ policies rather than adding broader grants.
Prove it
- Signed out: tables that should be private return empty or an expected error — not other users' rows.
- User A / user B: each sees only their rows (checklist in the Lovable and Bolt posts).
- Re-run the
role_table_grantsquery after migrations so grants match intent.
StackSiren's free scan checks what a signed-out visitor can reach on sites you own; use it after schema changes. Deeper product notes live in the Supabase RLS guide.
Related reading
- Supabase stops auto-exposing new tables on 30 October 2026
- Setting up Supabase RLS in a Lovable app
- Bolt + Supabase RLS checklist
- What does USING (true) mean in a Supabase RLS policy?
FAQ
Why did this start after I created a new table?
On projects that no longer auto-grant public tables to API roles (new projects since mid-2026, all projects from 30 Oct 2026), a table exists in Postgres but is invisible to the Data API until you GRANT. Existing tables keep prior grants.
Can I just disable RLS to clear 42501? No. 42501 is about privileges. Disabling RLS without fixing grants doesn't address the error, and disabling RLS with broad grants exposes rows. Fix grants, keep RLS on, add policies.
Is granting to authenticated enough for my React app?
Only for signed-in users. Calls made before login use anon. If the UI loads data while signed out, you need an anon grant and policies that make sense for public access — or change the UI to wait for a session.
Why does the SQL editor work but my app doesn't? The SQL editor typically runs as a privileged role. Your app uses the publishable/anon key (and user JWT when signed in). Grants and RLS apply to those API roles.
Does grant … to service_role fix my front end?
No. The browser must not use service_role. Grant and policy for authenticated / anon instead; keep secret keys on the server.
How do I stop Lovable/Bolt from recreating the issue?
Paste a standing instruction: every new table migration must include minimum GRANTs, ENABLE ROW LEVEL SECURITY, and auth.uid() policies — and must not grant all tables to anon or use the secret key in client code. Details in the grant-change post.
Fix guides for this topic
Platform checklists: Lovable · Bolt