{"slug": "supabase-s-oct-30-grants-change-what-breaks-and-the-fix-that-leaks", "title": "Supabase's Oct 30 grants change: what breaks, and the fix that leaks", "summary": "Supabase will stop automatically granting Data API privileges on newly created tables in the public schema for all existing projects on October 30, 2026, following a gradual rollout that began May 30 for new projects. The change means new tables stay invisible to supabase-js, PostgREST and GraphQL until an explicit GRANT is issued, and the bulk-grant workaround suggested in the announcement's rollback section re-exposes every table in public, including any with row-level security disabled. The writeup recommends per-table grants combined with RLS and policies in a single migration, plus a pg_class query using has_any_column_privilege to flag tables readable by anon with RLS off.", "body_md": "If you ship on Supabase, there's a platform change on **October 30, 2026** worth five minutes of your time. Short version: a **new** table in `public` will no longer be reachable by the Data API until you `GRANT` it. Existing tables keep working. The trap is the quick fix for the error it throws — it can re-expose every table you have with RLS off.\n\nThis is a cross-post. The original, with the full SQL and a copy-paste check, lives here: [Supabase's Oct 30 grants change](https://shipsealed.com/rls/supabase-october-30-grants-change/).\n\nUntil now, every table created in `public` got `select / insert / update / delete` granted to `anon`, `authenticated` and `service_role` automatically. On Oct 30 Supabase turns that off for all existing projects (new projects have had it off since a gradual rollout that started May 30).\n\n`GRANT`, they're invisible to the Data API (` supabase-js`, PostgREST, GraphQL), however you created them: SQL editor, migration, CLI, MCP or an AI tool.\nIf you only talk to your database over a direct Postgres connection (psql, an ORM, a server with a connection string), this doesn't affect you. Source: [the official announcement](https://github.com/orgs/supabase/discussions/45329).\n\nThe first new table after the cutover returns this instead of rows:\n\n```\n{\n  \"code\": \"42501\",\n  \"message\": \"permission denied for table invoices\",\n  \"hint\": \"Grant the required privileges to the current role with: GRANT SELECT ON public.invoices TO anon;\"\n}\n```\n\nPostgres rejects the query before RLS even runs. That's the new default working as intended, not a bug in your policies.\n\nThe fastest way to make that error disappear is the bulk grant from the announcement's rollback section:\n\n```\n-- don't paste this as a fix\ngrant select, insert, update, delete on all tables in schema public\n  to anon, authenticated, service_role;\n```\n\nIt works — and it also grants **every** table in `public`, including the ones you never meant to expose. Any of those with **RLS off** is now readable (and maybe writable) by anyone holding your anon key, which ships in your frontend. The new default was your chance to stop exposing tables by accident; the bulk grant gives that back. Ask an AI coding tool to \"fix the permission error\" and this line is a likely answer.\n\nGrant per table instead, and only what each role needs.\n\nTreat grant, RLS and policies as one unit, in the same migration, so a table never exists half-configured:\n\n```\ncreate table public.invoices (\n  id         bigint generated always as identity primary key,\n  user_id    uuid not null references auth.users(id),\n  amount     numeric not null\n);\n\nalter table public.invoices enable row level security;\n\ncreate policy \"owner reads\" on public.invoices for select to authenticated\n  using (user_id = (select auth.uid()));\n\n-- only the role and the operations the app actually uses\ngrant select on public.invoices to authenticated;\n```\n\nNo grant to `anon` here, because signed-out visitors don't read invoices. If a table uses `serial` / `bigserial` instead of an identity column and the API inserts into it, the sequence needs its own grant: `grant usage on sequence public.invoices_id_seq to authenticated;`\n\nRun this in the SQL editor. It lists every table and view in `public`, whether `anon` or `authenticated` can read it, and whether RLS is on:\n\n```\nselect c.relname,\n       has_any_column_privilege('anon', c.oid, 'select')          as anon_can_read,\n       has_any_column_privilege('authenticated', c.oid, 'select') as auth_can_read,\n       c.relrowsecurity                                           as rls_on\nfrom pg_class c\njoin pg_namespace n on n.oid = c.relnamespace\nwhere n.nspname = 'public' and c.relkind in ('r', 'p', 'v', 'm')\norder by 1;\n```\n\nAny row with `anon_can_read = true` and `rls_on = false` is a table anyone can read with your anon key. Using `has_any_column_privilege` (instead of the usual `information_schema.table_privileges`) catches **column grants** too — `grant select (id, email) on profiles to anon` doesn't show up as a table privilege, but the API still serves those columns.\n\nThe new default only protects you while every migration stays honest. Two free, MIT checks help:\n\n`npx airlock-migrate`` SUPABASE_DB_URL=... npx airlock-rls` checks the running database for tables with RLS off and policies that let everyone through.\nAnd if you just want to know *right now* whether your deployed app leaks — a `service_role` key in the browser, or a table anyone can read without logging in — there's a free scanner that checks from outside, reading only row counts, never your data: [shipsealed.com/service-role-scanner](https://shipsealed.com/service-role-scanner/).\n\n*Full guide with every SQL block tested: [Supabase's Oct 30 grants change](https://shipsealed.com/rls/supabase-october-30-grants-change/).*", "url": "https://wpnews.pro/news/supabase-s-oct-30-grants-change-what-breaks-and-the-fix-that-leaks", "canonical_source": "https://dev.to/mateuszingano/supabases-oct-30-grants-change-what-breaks-and-the-fix-that-leaks-28j9", "published_at": "2026-10-18 04:00:03+00:00", "updated_at": "2026-10-18 04:10:01.869783+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools"], "entities": ["Supabase", "PostgREST", "supabase-js", "Postgres"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/supabase-s-oct-30-grants-change-what-breaks-and-the-fix-that-leaks", "markdown": "https://wpnews.pro/news/supabase-s-oct-30-grants-change-what-breaks-and-the-fix-that-leaks.md", "text": "https://wpnews.pro/news/supabase-s-oct-30-grants-change-what-breaks-and-the-fix-that-leaks.txt", "jsonld": "https://wpnews.pro/news/supabase-s-oct-30-grants-change-what-breaks-and-the-fix-that-leaks.jsonld"}}