How to fix: Error 42501: Permission denied for schema public in Next.js with Supabase
This Supabase failure usually means the role your app uses (anon or authenticated) has no privilege on the table or schema, so Postgres refuses with 42501 before any RLS policy runs. A policy cannot fix it; a GRANT can. Since Supabase's 2026 change, new tables can start without these grants. Grant only what the app needs, keep RLS on, retest.
This pattern is common in AI-built Next.js and Supabase apps because generated code often leaves auth, cookie, deployment, or type boundaries unfinished. The exact symptom matters, so preserve the original error before changing code.
Why it happens
The database role the app uses (`anon` or `authenticated`) has no privilege on that table, so Postgres refuses before any RLS policy is read. A policy cannot fix it; a GRANT can. Since Supabase's 2026 change, tables created without an explicit grant start this way.
What to check
Run `select has_table_privilege('authenticated', 'public.your_table', 'select');` in the SQL editor. If it returns `f`, the grant is missing.
Read the exact message: `permission denied for schema` means the role lacks USAGE on that schema (`grant usage on schema public to authenticated;` for public); `permission denied for sequence` means a serial column's sequence needs USAGE.
Check whether the table was created by a migration or by a role other than the one that set the default privileges: those tables start with no grants for the API roles.
Confirm row level security is ON for the table before granting, so the grant does not open every row.
Fix plan
Grant only what the app needs, in the same migration that creates the table: `grant select, insert, update, delete on public.your_table to authenticated;` (and `select` to `anon` only for public data).
For a custom schema add `grant usage on schema your_schema to authenticated;`; for a serial id add `grant usage on sequence public.your_table_id_seq to authenticated;`.
Keep RLS on and keep the per-user policies: after the grant, RLS still filters rows per user.
Re-run the failing request, then the has_table_privilege check, and keep both outputs as proof.
When to stop guessing
If this touches auth, RLS, database writes, storage, redirects, or deployment callbacks, a build-only fix is not enough. Verify the real user path against the same Supabase project and domain that failed.
Need a second set of eyes? Paste the exact error into the free diagnosis form and get a focused rescue plan before you spend more time guessing.