# Supabase "permission denied for table": why it happens and how to fix it

By Rotomal team · Updated 2026-11-03 · Part of [Launch Lock](https://rotomal.com/ai-app-security-audit/)

**Short answer:** "permission denied for table" with code 42501 means the role making the request (anon for logged-out visitors, authenticated for signed-in users) has no privilege on that table. The most common cause now is a table created after Supabase stopped granting new public tables to the API roles automatically, scheduled for existing projects on 30 October 2026: fix it with a GRANT for the role that needs it, then enable Row Level Security and add policies. Other causes are a custom schema that is not exposed, the auth or vault schema, restricted columns, or an insert or update that RLS blocks. A select that RLS blocks returns empty results, not this error.

## Read the error first

The Data API (supabase-js, REST at /rest/v1/) returns the Postgres error as JSON. The role in the hint tells you who was refused:

```
{
  "code": "42501",
  "message": "permission denied for table your_table",
  "hint": "Grant the required privileges to the current role with: GRANT SELECT ON public.your_table TO anon;"
}
```

anon means the request carried only the publishable (anon) key, with no signed-in user. authenticated means a signed-in user. If you expected a signed-in request and see anon, the client is not sending the user's session.

## Cause 1: the table has no grant for the role

Supabase's changelog scheduled 30 October 2026 as the date existing projects stop granting anon, authenticated and service_role access to new tables in public; new projects have worked this way by default since a gradual rollout from 30 May 2026. Tables that already existed keep their grants, so the error typically appears on a table a migration, AI builder or script created afterwards. Check which roles have which privileges:

```
select grantee, privilege_type
from information_schema.role_table_grants
where table_name = 'your_table';
```

```
-- grant only what each role needs, then protect rows with RLS
grant select on public.your_table to authenticated;
grant select, insert, update, delete on public.your_table to service_role;
alter table public.your_table enable row level security;
create policy "read own rows" on public.your_table
  for select to authenticated
  using (auth.uid() = user_id);
```

Add grant select ... to anon only for data that logged-out visitors should read. Our guide to the 30 October 2026 change has the full timeline and Supabase's opt-in and rollback statements.

## Cause 2: a custom schema that is not exposed

Tables outside public need two things: the schema listed under Exposed schemas in the Data API settings, and grants on the schema itself. Supabase's custom-schema guide gives these statements (replace myschema; it grants ALL, so narrow them for private data):

```
grant usage on schema myschema to anon, authenticated, service_role;
grant all on all tables in schema myschema to anon, authenticated, service_role;
grant all on all routines in schema myschema to anon, authenticated, service_role;
grant all on all sequences in schema myschema to anon, authenticated, service_role;
alter default privileges for role postgres in schema myschema
  grant all on tables to anon, authenticated, service_role;
alter default privileges for role postgres in schema myschema
  grant all on routines to anon, authenticated, service_role;
alter default privileges for role postgres in schema myschema
  grant all on sequences to anon, authenticated, service_role;
```

## Cause 3: the auth or vault schema

The API roles cannot read the auth and vault schemas, and Supabase says that is intentional; it includes foreign data wrappers that rely on vault. Do not grant these schemas to anon or authenticated. Keep the user data you need in a public profiles table, or read it through a carefully written security definer function.

## Cause 4: restricted columns and select *

If some columns are revoked from a role, any query that touches them fails with 42501, including select *, which expands to every column. Select only the columns the role may read.

## Cause 5: Row Level Security on insert or update

When anon or authenticated inserts or updates a row and no RLS policy allows it, Postgres also returns 42501. Add a policy for that command (for insert and update, a with check clause), rather than turning RLS off.

## Permission denied or empty results?

- permission denied for table (42501) on a select: a grant is missing (causes 1 to 4).
- An empty array with no error: the grant is there, but RLS lets no rows through. PostgreSQL applies default deny when RLS is on and no policy matches, so the query returns zero rows.
- 42501 on insert or update with grants in place: RLS refused the write (cause 5).
- Works in the SQL editor but not in the app: the SQL editor runs as a privileged role, the app as anon or authenticated. Test as the app's role.

## The fix that creates a leak

The quickest way to silence the error is grant select, insert, update, delete on the table to anon. If RLS is off on that table, anyone with your public key can then read and change every row. Supabase's RLS guide says tables in an exposed schema need RLS enabled. Grant the least you need, enable RLS, and test as a logged-out visitor and as a second user before you ship. Launch Lock does that test from outside for a one-time fee and gives you the corrected SQL for each finding.

## Find 42501 errors in your logs

Supabase's troubleshooting guide includes a query against the Postgres logs that filters for SQL state 42501, so you can see which tables and roles are being refused across the app, not just the one screen you tested.

## FAQ

### What does Supabase error 42501 mean?

Insufficient privilege: the role making the request (usually anon or authenticated) is not allowed to do that on that table, schema or column, or an RLS policy refused an insert or update.

### Why do I get permission denied for a new table but not old ones?

Supabase scheduled 30 October 2026 as the date existing projects stop granting new public tables to the API roles automatically. Old tables keep their grants; a new table needs explicit GRANT statements, plus RLS and policies.

### Should I grant all privileges to anon to fix it?

No. Grant only what logged-out visitors genuinely need, often nothing. Granting everything to anon on a table without RLS makes the whole table readable and writable with your public key.

### Is permission denied the same as RLS returning no rows?

No. Permission denied (42501) on a read means a grant is missing. RLS filtering returns an empty result without an error. On writes, an RLS refusal also returns 42501.

### Does the service_role key bypass this?

The service_role key bypasses RLS, but after the change it still needs a grant on new tables. Never use it in browser or mobile code.

## Sources

- Supabase troubleshooting: Database API 42501 errors https://supabase.com/docs/guides/troubleshooting/database-api-42501-errors
- Supabase changelog: Breaking change: tables not exposed to Data and GraphQL API automatically https://supabase.com/changelog/45329-breaking-change-tables-not-exposed-to-data-and-graphql-api-automatically
- Supabase docs: Using custom schemas https://supabase.com/docs/guides/api/using-custom-schemas
- Supabase docs: Data API https://supabase.com/docs/guides/database/data-api
- Supabase docs: Row Level Security https://supabase.com/docs/guides/database/postgres/row-level-security
- PostgreSQL docs: Row Security Policies (default deny) https://www.postgresql.org/docs/current/ddl-rowsecurity.html

[Launch Lock](https://rotomal.com/ai-app-security-audit/) does this for you. Request it free, no card: https://rotomal.com/ai-app-security-audit/request/
---
Rotomal · https://rotomal.com/ai-app-security-audit/supabase-permission-denied-for-table/ · hello@rotomal.com
