Changelog

New updates and product improvements

undefined RSS

New tables in the public schema will no longer be exposed to the Data API automatically.

When this change takes effect#

  • Starting today (April 28, 2026), you can create new Supabase projects where tables in the public schema are not exposed to the Data API and GraphQL API by default. You can enable this setting at project creation.
  • On May 18, 2026, pg_graphql will not be enabled by default. More details here.
  • On May 30, 2026 this setting becomes the default for all new projects.
  • On October 30, 2026 the setting will be applied it to all existing projects.

Once the change is rolled out to your project, new tables you create in public schema require an explicit opt-in (via a Postgres grant ) before the Data API can see them. Existing tables are not affected in your project, they keep their current grants and stay reachable. This change applies to projects that use the Data API, Supabase's auto-generated REST and GraphQL layer, which is what supabase-js and our other client libraries call. If your app reads and writes Postgres over a direct connection (via psql, ORM or an app server with a connection string), this change will not affect you.

What's changing#

Previously, when you create a Supabase project with default settings, select, insert, update, and delete are granted to every table in the public schema to the anon, authenticated and service_role roles. Every table you create becomes reachable via the Data API on creation.

From today, the project creation screen includes a setting to opt out of those default grants. When the “Default privileges for new entities” checkbox is unchecked, you opt-in to the new behavior and tables aren’t exposed to the Data API by default. On May 30, 2026 the opt-out becomes the default for all new projects.

RLS behavior remains unchanged. Grants are a separate layer: they control whether a role can access a table at all, while RLS controls which rows that role can see.

If a grant is missing, PostgREST returns a clear error rather than a silent failure:


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

The hint shows you which role is missing which privilege, along with the GRANT needed to fix it.

Before → After#

StepBeforeAfter
Create table in publicTable is reachable via Data API on creationTable exists but is not reachable via Data API
grant to anon / authenticated / service_roleImplicit, via default privilegesRequired, explicit grant statement
alter table ... enable row level securityRemains the sameRemains the same
create policy ...Remains the sameRemains the same

Why#

When Supabase launched, a human reviewed each schema change and enabled RLS on new tables as they went. The default grants made this convenient: create a table, and it showed up in your client.

That model doesn’t scale, and it’s easy to accidentally expose new tables before you’ve secured them.Today, agents, CLI scripts, and AI platforms create tables too, and many of those operations do not have a human reviewing the diff.

Explicit grants make access a deliberate, code-level decision. The PostgREST error response above includes a precise hint, so an agent can self-correct when a grant is missing instead of producing a broken request.

Without a grant, the API cannot see the table, regardless of how you created it (SQL editor, migrations, Management API, MCP, CLI, or an AI coding tool). Postgres enforces this at the role layer, so the guarantee holds regardless of the creation path.

We’re moving the platform toward declarative code. Explicit Postgres grants are reviewable, diffable, and greppable. They also give you per-role control: anon and authenticated need different privileges in most schemas, and an explicit grant makes that difference visible in your migrations. This was always possible, now it's the default.

Currently, the default grants expose every table in public over the Data API on creation, including tables a developer forgot to protect.

This is the next step in a series of platform default changes we have shipped over the past quarter:

Who is affected#

You are unaffected if you only talk to your database over a direct Postgres connection--you can stop reading.

You need to act if any of the following is true:

  1. Your app reads or writes tables in the public schema via the Data API (PostgREST, GraphQL, supabase-js, any of the client libraries, or direct HTTP to /rest/v1/ or /graphql/v1 )
  2. Your migrations or provisioning flow create tables in public without explicit GRANT statements.
  3. Your AI coding tool, CLI script, or Management API call creates tables and expects them to be reachable over the Data API on creation.

The change reaches you on one of three dates:

  • From Today, if you opt out of the "Default privileges for new entities" setting during project creation.
  • May 30, 2026, when the new behavior becomes the default for all new projects.
  • October 30, 2026, when the new behavior is enforced on all existing projects.

Between now and then, Security Advisor will flag affected tables, and we’ll email active projects so you can review access, add grants, and verify your app.

What to do#

For new tables you want to expose via the Data API, make explicit grants part of your table-creation flow. Without an explicit GRANT , a role will not have access to the created table.

Treat these three steps as a unit. If the grant is missing, Postgres rejects the query before RLS comes into play.


_14
-- 1. Grant the privileges the role needs
_14
grant select on public.your_table to anon;
_14
grant select, insert, update, delete on public.your_table to authenticated;
_14
grant select, insert, update, delete on public.your_table to service_role;
_14
_14
-- 2. Enable RLS
_14
alter table public.your_table enable row level security;
_14
_14
-- 3. Add the policies you need
_14
create policy "users can read their own rows"
_14
on public.your_table
_14
for select
_14
to authenticated
_14
using (auth.uid() = user_id);

If you use an AI coding tool to create tables, update its system prompt or adopt the Supabase agent skill, which includes the grants step, and which we keep updated as the platform changes. Skills are easy to install, and handle grants correctly by default. Agents can self-correct from the PostgREST error hint returned as well.

If you run your own migration framework, or a platform that provisions Supabase projects for your own customers, bundle the GRANT statements alongside your ENABLE ROW LEVEL SECURITY and policy statements in the same migration.

Opting in on existing projects#

You do not have to wait for October 30. To adopt the new behavior on an existing project today, run the same revoke statements the dashboard runs at project creation.

Open the SQL Editor for your project and run:


_10
-- Stop Postgres from granting default privileges on future objects in public
_10
alter default privileges for role postgres in schema public
_10
revoke select, insert, update, delete on tables from anon, authenticated, service_role;
_10
_10
alter default privileges for role postgres in schema public
_10
revoke usage, select on sequences from anon, authenticated, service_role;

These four statements change the defaults for future tables and sequences that the postgres role creates in the public schema.

Existing objects keep their current grants, so your running app stays reachable.

From this point on, new tables you create in the public schema need explicit GRANT statements before the Data API can see them.

If you also want to tighten grants on existing tables you do not want reachable via the Data API, revoke them per table:


_10
revoke all on table public.your_table from anon, authenticated, service_role;

The Data API exposure badge in the Table Editor and the Security Advisor list the tables worth reviewing.

FAQ#

Does this affect tables in the storage, auth, realtime, or custom schemas?

No. The change touches default privileges in the public schema. Tables in storage, auth, realtime, and any custom schemas you expose via the Data API keep their current grants and their current defaults.

I opted in on an existing project, created new tables, and my app is broken. How do I roll back?

Open the SQL Editor and run two blocks.

1) Restore defaults for future tables

Future tables will now behave as they did before the revoke:


_10
alter default privileges for role postgres in schema public
_10
grant select, insert, update, delete on tables to anon, authenticated, service_role;
_10
_10
alter default privileges for role postgres in schema public
_10
grant usage, select on sequences to anon, authenticated, service_role;

2) Fix existing tables

alter default privileges only affects future objects, so tables you created since the revoke stay without grants. Grant them in bulk:


_10
grant select, insert, update, delete on all tables in schema public to anon, authenticated, service_role;
_10
grant usage, select on all sequences in schema public to anon, authenticated, service_role;

After running both blocks, your project matches the pre-revoke state. If you added tables you want to keep private, revoke those individually after the bulk grant:


_10
revoke all on table public.your_private_table from anon, authenticated, service_role;

Rollout timeline#

DateMilestoneUser action
2026-04-28Changelog published; opt-in toggle available at project creation; docs updatedTry it on a test project; adapt your provisioning flow
2026-05-30New behavior becomes the default for all new projectsNew-project workflows must include explicit GRANT statements
2026-10-30New behavior enforced on all existing projectsMigration must be complete; tables without explicit grants stop being reachable via the Data API

Communications timeline#

We email owners and admins of every active project, including projects with no Security-Advisor-flagged tables today.

After October 30, any new table created in public without an explicit grant stops being reachable via the Data API, so a project with no flagged tables today still breaks the first time someone adds a new one.

From today through October 30, the Security Advisor flags affected tables per project and shows the remediation SQL, so you do not have to hunt for them yourself.

DateChannelAudience
2026-04-28This changelog postPublic
2026-05-07April monthly newsletterAll subscribers
2026-05-13Email: first notice of the May 30 change for new projectsOwners and admins of all active projects
2026-05-27Email: final reminder before the May 30 change for new projectsOwners and admins of all active projects
2026-09-23Email: five-week notice before the October 30 change for existing projectsOwners and admins of all active projects
2026-10-23Email: final notice, one week before change for existing projectsOwners and admins of all active projects

If you have a question, create a support ticket here.

Verify the correctness of your RLS policies with the RLS tester#

Verifying the correctness of your RLS policies set up has always been a gap, as highlighted by a number of GitHub discussions like here and here. As such, we're piloting a dedicated UI for RLS testing (using role impersonation as the base), in which you'll be able to

  • Run a SQL query as a user (not logged in / logged in - this is the role impersonation part)
  • See which RLS policies are being evaluated as part of the query
  • And hopefully be able to debug which policies are not set up correctly
  • (Side note) We also added partial support for testing client library code instead of just SQL
    • This is powered by the AI Assistant which will then infer the code into SQL
    • e.g const { data } = await client.from('colors').select('*')
    • Given that this is by AI, as always do verify the output!

Note: Only SELECT queries are supported for now to simplify things. Testing mutation queries (INSERT, UPDATE, DELETE) could trigger side effects such as triggering a database trigger, especially if it involves an external request such as an edge function or HTTP call - we're keeping them out of this preview while we work out a safe way to support them.

Changes are currently set as a feature preview which you can access by clicking on your Profile picture in the top navigation bar . We'll iterate as we get feedback from everyone so please do let us know what you think! 🙂🙏

Related PR: https://github.com/supabase/supabase/pull/45121

What we'd like to know from you#

  • Any bugs or issues that you might have run into while using the RLS tester
  • Any ideas or suggestions that you reckon will improve the DX based on how you currently verify the correctness of your RLS policies
  • Feel free to leave any feedback in this thread too! (Both good and bad!) 🙂🙏

All official Supabase client libraries now automatically retry failed database queries when they encounter transient network errors — no code changes required.

What changed#

When a GET or HEAD request to PostgREST fails with a transient error (HTTP 520, HTTP 503, or a network-level failure), the client transparently retries the request up to 3 times with exponential backoff (1s → 2s → 4s, capped at 30s). Each retry includes an X-Retry-Count header so server-side logs can observe retry behavior.

Only idempotent methods (GET and HEAD) are retried. POST, PATCH, PUT, and DELETE requests are never retried automatically.

Versions#

LibraryVersion
supabase-jsv2.102.0
supabase-swiftv2.43.0
supabase-flutter (postgrest_dart)v2.7.0 (supabase_flutter v2.12.2+)
supabase-pyv2.29.0

Configuration#

Retries are enabled by default. You can opt out globally or per-request:

JavaScript


_10
// Disable globally
_10
const supabase = createClient(url, key, {
_10
db: { retryEnabled: false }
_10
})
_10
_10
// Disable per request
_10
const { data } = await supabase.from('table').select().retry(false)

Swift


_10
// Disable globally
_10
let client = PostgrestClient(url: url, headers: headers, retryEnabled: false)
_10
_10
// Disable per request
_10
let data = try await client.from("table").select().retry(enabled: false).execute()

Flutter/Dart


_10
// Disable globally
_10
final client = PostgrestClient(url, retryEnabled: false);
_10
_10
// Disable per request
_10
final data = await supabase.from('table').select().retry(enabled: false);

Python


_10
# Disable per request
_10
data = supabase.table("table").select("*").retry(False).execute()

2026
2025
2024
2023
2022
2021

Build in a weekend, scale to millions