DEV Community

Cenk KURTOĞLU
Cenk KURTOĞLU

Posted on

Supabase "permission denied" After October 30: The Exact Fix (Copy-Paste SQL)

If your Supabase app suddenly starts throwing permission denied for table errors on October 30, 2026 — your database is not broken. Supabase is tightening default table permissions, and if your app relied on the old defaults, it will break.

Here is the exact fix, and how to check today whether you're affected.

The error you'll see

It arrives in a few flavors:

PostgresError: permission denied for table users
Enter fullscreen mode Exit fullscreen mode
permission denied for schema public
Enter fullscreen mode Exit fullscreen mode
new row violates row-level security policy for table "profiles"
Enter fullscreen mode Exit fullscreen mode

The first two are direct GRANT problems. The third appears when a policy you rely on silently stopped applying because the role lost its underlying table access.

Why this happens on October 30

Supabase is changing default GRANTs on the anon, authenticated, and service_role roles. Historically, every table in public was readable/writable by all three roles by default, with RLS policies as the only gate. After the change, tables only get access that you explicitly grant.

If your schema was created before this change and you never ran explicit GRANTs — because you never needed them — every query from anon or authenticated starts failing on October 30.

This is not a bug. It's a security hardening. But it's a breaking change for any app that relied on the defaults.

The 60-second check

Run this in your SQL editor today, before October 30:

select
  grantee,
  table_name,
  privilege_type
from information_schema.role_table_grants
where table_schema = 'public'
  and grantee in ('anon', 'authenticated')
order by table_name, grantee;
Enter fullscreen mode Exit fullscreen mode

If you get rows back for every table — you're fine. If you get zero rows, your app breaks on October 30.

The fix: explicit GRANTs

The pattern depends on what your tables actually need. The safe default that matches old behavior for most apps:

-- Read access for both client roles
grant select on all tables in schema public to anon, authenticated;

-- Write access only for logged-in users (if your app writes from the client)
grant insert, update, delete on all tables in schema public to authenticated;
Enter fullscreen mode Exit fullscreen mode

But don't blind-copy that. The right move is to grant only what your RLS policies already assume:

Policy type Minimum grant
for select to anon grant select on <table> to anon
for all to authenticated grant select, insert, update, delete on <table> to authenticated
Insert-only (logs, events) grant insert on <table> to authenticated

And for future tables, lock it in with default privileges:

alter default privileges in schema public
  grant select on tables to anon, authenticated;

alter default privileges in schema public
  grant insert, update, delete on tables to authenticated;
Enter fullscreen mode Exit fullscreen mode

The order matters: GRANTs are the floor, RLS is the gate

A common confusion after this change: "I have RLS with using (true) for anon reads — why is it still permission denied?"

Because RLS policies filter rows, but GRANTs permit access in the first place. A policy without a GRANT is a gate on a road that no longer reaches your property. After October 30 you need both:

GRANT (can you touch this table at all?)
  → RLS policy (which rows can you see?)
    → your query succeeds or fails
Enter fullscreen mode Exit fullscreen mode

Test the fix without breaking prod

-- simulate the new defaults as a dry run
begin;
revoke all on all tables in schema public from anon, authenticated;
-- ^ this is what Oct 30 effectively does

-- now run your app's read paths as a test:
set local role authenticated;
select * from public.profiles limit 1; -- expect: permission denied = you're affected
rollback;
Enter fullscreen mode Exit fullscreen mode

If the set local role query fails, you found a table that needs an explicit GRANT before October 30. The rollback puts everything back.

Storage buckets are affected too

If you use Supabase Storage with client-side uploads, the same principle applies to storage objects. Policies on storage.objects still need the underlying access. Errors like new row violates row-level security policy for table "objects" on upload mean the same thing: GRANT first, policy second.

The migration I'm using

I maintain a free readiness checker and a paid migration kit for exactly this change:

  • Free readiness check — scans your repo for GRANT statements, flags tables that rely on defaults, gives you the exact SQL to run: github.com/cekuu35/alexa-mcp-server (MCP tool: oct30_readiness_check)
  • Migration Kit ($29) — audit script, 5 migration templates (read-only apps, client-write apps, service-role-only apps), a testing script that simulates the new defaults against your schema, and a checklist: cenkkurtoglu.gumroad.com/l/supabase-oct30-migration-kit

One more thing: don't panic-fix with USING(true)

When permission errors hit, the worst instinct is grant all on all tables to anon or dropping RLS entirely. That fixes the error by removing the security layer. October 30 is a hardening event — the correct response is to make your implicit permissions explicit, not to open everything wider than it was before.

If your policies are correct and your GRANTs match them, October 30 passes without a single error in your logs.


References: Supabase discussion #51482 (permission denied reports), the Supabase docs on table GRANTs, and hands-on migration testing against pre-Oct-30 schemas.

Top comments (0)