Supabase Error: Fix "permission denied for table" (42501)

11. August 2026 approx. 4 min read Supabase
Contents 10

Your RLS policies look fine – yet the Supabase client throws:

permission denied for table projects
ERROR: 42501

Or PostgREST shows 42501 / “permission denied”. This is not the same as new row violates row-level security policy. Here is how to tell them apart and fix the grants.

The Problem: GRANT Runs Before RLS

Postgres checks privileges in layers. Table-level GRANTs for the role your request uses (anon or authenticated) are evaluated before Row Level Security. If the role has no privilege on the table, Postgres stops with 42501 – your policies never run.

Typical triggers:

  • Table created via raw SQL / Prisma / Drizzle without grants to authenticated / anon
  • Wrong key: calling with the anon key while only authenticated has grants
  • Schema / table exposed in the API but privileges missing after a migration

If you instead see new row violates row-level security policy, you already passed grants – fix the policies (or use returning: 'minimal'). See the RLS insert error guide.

The Solution: Grant the API Roles Explicitly

Step 1: Confirm the Error Code

const { data, error } = await supabase.from('projects').select('*')
if (error) {
  console.error(error.code, error.message)
  // 42501 / "permission denied for table …" → grants
  // "new row violates row-level security policy" → RLS policies
}

Step 2: Check Current Grants

SELECT grantee, privilege_type
FROM information_schema.role_table_grants
WHERE table_schema = 'public'
  AND table_name = 'projects'
ORDER BY grantee, privilege_type;

You want the role that matches your client call (authenticated for logged-in users, anon only if anonymous access is intended).

Step 3: Grant Least Privilege

-- Logged-in users via the anon/publishable key + user JWT
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE public.projects TO authenticated;

-- Only if anonymous clients must read/write (rare for writes)
-- GRANT SELECT ON TABLE public.projects TO anon;

-- Sequences for serial / identity columns
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO authenticated;

Keep RLS enabled after granting. Grants open the door; policies decide which rows get through.

Step 4: Reload the API Schema (If Needed)

After creating tables or changing grants, PostgREST may need a schema reload (Dashboard → Settings → API → Reload, or wait for cache refresh). A missing grant can also look like “table not found in schema cache”.

Common Mistakes

  • Writing more RLS policies for a 42501: policies never run until grants exist.
  • Granting only to yourself / postgres: the SQL editor works; the JS client still fails.
  • Broad grants to anon without RLS: security hole – prefer authenticated + tight policies.
  • Confusing with Storage 403: Storage uses storage.objects policies – see Storage 403 RLS on upload.

Troubleshooting Checklist

  1. ✅ Error is 42501 / permission denied for table (not RLS violate)?
  2. ✅ Client uses the intended key + session?
  3. role_table_grants shows privileges for authenticated (or anon)?
  4. ✅ RLS still enabled with real policies after the grant?
  5. ✅ API schema reloaded if the table is brand new?

Conclusion

permission denied for table is a missing GRANT, not a broken RLS policy. Grant the API role, keep RLS on, then tune policies. For the other classic message – insert blocked by RLS – use the dedicated row-level security policy fix.

Still stuck on 42501? Drop the table name and whether you call as anon or authenticated in a comment – no secrets.

Comments