
Supabase が Data API のデフォルト動作を変更します。**2026年5月30日**から新規プロジェクト、**10月30日**から既存プロジェクトで、`public` スキーマに作成したテーブルが Data API に**自動公開されなくなります**。
「え、今まで自動公開だったの?」と思った方——そうです。Supabase はこれまで `public` スキーマのテーブルを PostgREST(Data API)と GraphQL API に自動的に露出させていました。RLS(Row Level Security)で行レベルの制御はできましたが、テーブルの存在自体は API から見える状態でした。
この記事では、変更の全容と、既存プロジェクトを明示的 GRANT に移行する具体的な手順を紹介します。
## 何が変わるのか
### Before:暗黙の全公開
これまでの Supabase は、`public` スキーマにテーブルを作成すると、`anon`・`authenticated`・`service_role` の3ロールに対して `SELECT, INSERT, UPDATE, DELETE` が自動的に付与されていました。
```sql
-- 今までの暗黙の動作(ユーザーが書かなくても裏で実行されていた)
alter default privileges in schema public
grant all on tables to anon, authenticated, service_role;
```
つまり `CREATE TABLE` した瞬間に Data API から叩ける状態です。
### After:明示的 GRANT が必須
5月30日以降の新規プロジェクト(10月30日以降は全プロジェクト)では、テーブルを作成しただけでは Data API に露出しません。`GRANT` 文で明示的にアクセスを許可する必要があります。
```sql
create table public.posts (
id uuid primary key default gen_random_uuid(),
user_id uuid references auth.users(id) not null,
title text not null,
body text,
created_at timestamptz default now()
);
-- これがないと Data API から見えない
grant select on public.posts to anon;
grant select, insert, update, delete on public.posts to authenticated;
alter table public.posts enable row level security;
create policy "自分の投稿のみ操作可能"
on public.posts
for all
to authenticated
using (auth.uid() = user_id);
```
GRANT を書き忘れると、PostgREST はエラーコード `42501` を返します。エラーメッセージには修正に必要な SQL が含まれるので、見落としても気づけます。
## ロールアウトの4段階
| 日付 | 内容 |
|------|------|
| 4月28日 | プロジェクト作成時にオプトイン可能(「自動公開」チェックボックス) |
| 5月18日 | `pg_graphql` が新規プロジェクトでデフォルト無効に |
| **5月30日** | **新規プロジェクトで自動公開がデフォルトOFF** |
| **10月30日** | **既存プロジェクトにも適用** |
既存テーブルの GRANT は剥奪されません。影響を受けるのは**適用日以降に作成する新しいテーブル**です。
## なぜこの変更が必要なのか
### 1. 暗黙の公開は事故のもと
RLS を設定する前のわずかな時間でも、テーブルは API 経由で読み書きできる状態でした。開発中に `CREATE TABLE` してから RLS ポリシーを書くまでの間、データが露出するリスクがあります。
### 2. 宣言的コードとの親和性
明示的な GRANT 文はマイグレーションファイルに残ります。`reviewable`、`diffable`、`greppable`——コードレビューで権限の変更を追跡できるようになります。
### 3. ロール別の権限が可視化される
`anon`(未認証)と `authenticated`(認証済み)では必要な権限が異なります。明示的 GRANT なら、その違いがマイグレーションファイルの中で一目瞭然です。
```sql
-- anon には SELECT のみ(公開情報の閲覧だけ)
grant select on public.posts to anon;
-- authenticated には CRUD を許可(RLS で行制御)
grant select, insert, update, delete on public.posts to authenticated;
```
### 4. AI コーディングツールとの相性
Cursor、Claude Code、GitHub Copilot——AI ツールがマイグレーションを自動生成する時代です。暗黙の公開に依存していると、AI が `CREATE TABLE` だけ生成して GRANT を忘れるケースが起きます。明示的 GRANT が必須になれば、AI ツールも(そしてそのプロンプトも)正しいパターンを学習します。
Supabase は [Agent Skills](https://github.com/supabase/agent-skills) というオープンソースの指示セットを公開しており、Claude Code や Copilot に正しいパターンを教えることができます。
## 実プロジェクトの Before / After
実際に運用中のプロジェクトで、どう変わるかを見てみます。
### Before:grant all パターン(現在の多くのプロジェクト)
```sql
-- 初期マイグレーションでよく見るパターン
grant all on all tables in schema public
to anon, authenticated, service_role, postgres;
alter default privileges in schema public
grant all on tables
to anon, authenticated, service_role, postgres;
```
この1行で全テーブルが全ロールに全権限公開されます。便利ですが、`anon` ロールに `DELETE` 権限が付いているのは過剰です。
### After:最小権限パターン(移行後)
```sql
-- テーブルごとに必要な権限だけを付与
grant select on public.posts to anon;
grant select, insert, update, delete on public.posts to authenticated;
grant select, insert, update, delete on public.posts to service_role;
grant select on public.profiles to anon;
grant select, update on public.profiles to authenticated;
grant select, insert, update, delete on public.profiles to service_role;
```
テーブルごとにロール×操作の組み合わせを明示します。コード量は増えますが、「何が公開されているか」が grep 一発で分かります。
```bash
# どのテーブルが anon に公開されているか一覧
grep -r "grant.*to anon" supabase/migrations/
```
## 移行の手順
### Step 1:現状を監査する
Supabase ダッシュボードの **Security Advisor** で、現在どのテーブルがどのロールに公開されているかを確認します。テーブルエディターには「Data API exposure」バッジも表示されるようになっています。
SQL で直接確認することもできます。
```sql
select
grantee,
table_name,
string_agg(privilege_type, ', ' order by privilege_type) as privileges
from information_schema.table_privileges
where table_schema = 'public'
and grantee in ('anon', 'authenticated', 'service_role')
group by grantee, table_name
order by table_name, grantee;
```
### Step 2:デフォルト権限を取り消す
既存プロジェクトで早期に移行する場合、まずデフォルトの自動付与を止めます。
```sql
-- 新しいテーブルへの自動 GRANT を停止
alter default privileges for role postgres in schema public
revoke select, insert, update, delete on tables
from anon, authenticated, service_role;
alter default privileges for role postgres in schema public
revoke execute on functions
from anon, authenticated, service_role;
alter default privileges for role postgres in schema public
revoke usage, select on sequences
from anon, authenticated, service_role;
```
このマイグレーションを適用すると、以降に作成するテーブルは明示的 GRANT がないと Data API に露出しなくなります。**既存テーブルの権限はそのまま残ります。**
### Step 3:テーブルごとの GRANT をマイグレーションに追加
既存テーブルの権限を最小化したい場合は、現在の `grant all` を取り消してからテーブルごとに再付与します。
```sql
-- まず全部取り消す
revoke all on all tables in schema public
from anon, authenticated;
-- テーブルごとに再付与
grant select on public.posts to anon;
grant select, insert, update, delete on public.posts to authenticated;
grant select on public.profiles to anon;
grant select, update on public.profiles to authenticated;
-- 内部テーブルは authenticated にも公開しない
-- (service_role のみ、または RPC 関数経由でアクセス)
```
### Step 4:ローカル開発環境を検証する
`supabase db reset` でマイグレーションを最初から再生し、アプリケーションが正常に動作することを確認します。GRANT がマイグレーションに含まれていれば、ローカルでも本番でも同じ権限構成が再現されます。
```bash
supabase db reset
npm run dev
# アプリの主要機能を手動テスト
```
### Step 5:CI で権限の整合性を保証する
マイグレーションの diff に `CREATE TABLE` があるのに `GRANT` がない——これを CI で検知できると安心です。
```bash
#!/bin/bash
# pre-commit hook or CI step
for migration in supabase/migrations/*.sql; do
tables=$(grep -oP 'create table (?:if not exists )?public\.(\w+)' "$migration" | \
grep -oP 'public\.\w+')
for table in $tables; do
if ! grep -q "grant.*on ${table}" "$migration"; then
echo "WARNING: ${migration} creates ${table} but has no GRANT statement"
fi
done
done
```
## 2層セキュリティモデルの再確認
この変更を機に、Supabase のセキュリティモデルを整理しておきます。
```
Layer 1: GRANT → テーブルに「アクセスできるか」を制御
Layer 2: RLS → テーブルの「どの行を操作できるか」を制御
```
GRANT がなければ RLS の出番すらありません。逆に、GRANT があっても RLS が無効なら全行が見えます。**両方セットで設計するのが正解**です。
マイグレーションでは、テーブル作成・GRANT・RLS を1ファイルにまとめるのがベストプラクティスです。
```sql
-- 1ファイルに全部まとめる
create table public.comments (...);
grant select on public.comments to anon;
grant select, insert, update, delete on public.comments to authenticated;
alter table public.comments enable row level security;
create policy "..." on public.comments ...;
```
## まとめ
| 項目 | 旧デフォルト | 新デフォルト |
|------|------------|------------|
| テーブル公開 | 自動 | 明示的 GRANT が必要 |
| GraphQL | デフォルト有効 | デフォルト無効 |
| 既存テーブル | 影響なし | 影響なし(新テーブルのみ) |
| 移行期限 | — | 既存プロジェクトは 10月30日 |
やることは明確です。
1. **今すぐ**:Security Advisor で現状を監査する
2. **5月30日まで**:新規プロジェクトを作る予定があれば、GRANT パターンに慣れておく
3. **10月30日まで**:既存プロジェクトのマイグレーションに GRANT を追加する
「暗黙の全公開」から「明示的な最小権限」へ。地味な変更ですが、Supabase を本番で運用するなら避けて通れないアップデートです。マイグレーションファイルに権限が宣言的に残る——これはむしろ歓迎すべき変化だと思います。
## 参考リンク
- [Breaking Change: Tables not exposed to Data and GraphQL API automatically](https://supabase.com/changelog/45329-breaking-change-tables-not-exposed-to-data-and-graphql-api-automatically)
- [Hardening the Data API - Supabase Docs](https://supabase.com/docs/guides/api/hardening-data-api)
- [Securing your API - Supabase Docs](https://supabase.com/docs/guides/api/securing-your-api)
- [Supabase Agent Skills - GitHub](https://github.com/supabase/agent-skills)

Supabase が Data API のデフォルト動作を変更します。2026年5月30日から新規プロジェクト、10月30日から既存プロジェクトで、public スキーマに作成したテーブルが Data API に自動公開されなくなります。
「え、今まで自動公開だったの?」と思った方——そうです。Supabase はこれまで public スキーマのテーブルを PostgREST(Data API)と GraphQL API に自動的に露出させていました。RLS(Row Level Security)で行レベルの制御はできましたが、テーブルの存在自体は API から見える状態でした。
この記事では、変更の全容と、既存プロジェクトを明示的 GRANT に移行する具体的な手順を紹介します。
何が変わるのか
Before:暗黙の全公開
これまでの Supabase は、public スキーマにテーブルを作成すると、anon・authenticated・service_role の3ロールに対して SELECT, INSERT, UPDATE, DELETE が自動的に付与されていました。
-- 今までの暗黙の動作(ユーザーが書かなくても裏で実行されていた)
alter default privileges in schema public
grant all on tables to anon, authenticated, service_role;
つまり CREATE TABLE した瞬間に Data API から叩ける状態です。
After:明示的 GRANT が必須
5月30日以降の新規プロジェクト(10月30日以降は全プロジェクト)では、テーブルを作成しただけでは Data API に露出しません。GRANT 文で明示的にアクセスを許可する必要があります。
create table public.posts (
id uuid primary key default gen_random_uuid(),
user_id uuid references auth.users(id) not null,
title text not null,
body text,
created_at timestamptz default now()
);
-- これがないと Data API から見えない
grant select on public.posts to anon;
grant select, insert, update, delete on public.posts to authenticated;
alter table public.posts enable row level security;
create policy "自分の投稿のみ操作可能"
on public.posts
for all
to authenticated
using (auth.uid() = user_id);
GRANT を書き忘れると、PostgREST はエラーコード 42501 を返します。エラーメッセージには修正に必要な SQL が含まれるので、見落としても気づけます。
ロールアウトの4段階
| 日付 |
内容 |
| 4月28日 |
プロジェクト作成時にオプトイン可能(「自動公開」チェックボックス) |
| 5月18日 |
pg_graphql が新規プロジェクトでデフォルト無効に |
| 5月30日 |
新規プロジェクトで自動公開がデフォルトOFF |
| 10月30日 |
既存プロジェクトにも適用 |
既存テーブルの GRANT は剥奪されません。影響を受けるのは適用日以降に作成する新しいテーブルです。
なぜこの変更が必要なのか
1. 暗黙の公開は事故のもと
RLS を設定する前のわずかな時間でも、テーブルは API 経由で読み書きできる状態でした。開発中に CREATE TABLE してから RLS ポリシーを書くまでの間、データが露出するリスクがあります。
2. 宣言的コードとの親和性
明示的な GRANT 文はマイグレーションファイルに残ります。reviewable、diffable、greppable——コードレビューで権限の変更を追跡できるようになります。
3. ロール別の権限が可視化される
anon(未認証)と authenticated(認証済み)では必要な権限が異なります。明示的 GRANT なら、その違いがマイグレーションファイルの中で一目瞭然です。
-- anon には SELECT のみ(公開情報の閲覧だけ)
grant select on public.posts to anon;
-- authenticated には CRUD を許可(RLS で行制御)
grant select, insert, update, delete on public.posts to authenticated;
4. AI コーディングツールとの相性
Cursor、Claude Code、GitHub Copilot——AI ツールがマイグレーションを自動生成する時代です。暗黙の公開に依存していると、AI が CREATE TABLE だけ生成して GRANT を忘れるケースが起きます。明示的 GRANT が必須になれば、AI ツールも(そしてそのプロンプトも)正しいパターンを学習します。
Supabase は Agent Skills というオープンソースの指示セットを公開しており、Claude Code や Copilot に正しいパターンを教えることができます。
実プロジェクトの Before / After
実際に運用中のプロジェクトで、どう変わるかを見てみます。
Before:grant all パターン(現在の多くのプロジェクト)
-- 初期マイグレーションでよく見るパターン
grant all on all tables in schema public
to anon, authenticated, service_role, postgres;
alter default privileges in schema public
grant all on tables
to anon, authenticated, service_role, postgres;
この1行で全テーブルが全ロールに全権限公開されます。便利ですが、anon ロールに DELETE 権限が付いているのは過剰です。
After:最小権限パターン(移行後)
-- テーブルごとに必要な権限だけを付与
grant select on public.posts to anon;
grant select, insert, update, delete on public.posts to authenticated;
grant select, insert, update, delete on public.posts to service_role;
grant select on public.profiles to anon;
grant select, update on public.profiles to authenticated;
grant select, insert, update, delete on public.profiles to service_role;
テーブルごとにロール×操作の組み合わせを明示します。コード量は増えますが、「何が公開されているか」が grep 一発で分かります。
# どのテーブルが anon に公開されているか一覧
grep -r "grant.*to anon" supabase/migrations/
移行の手順
Step 1:現状を監査する
Supabase ダッシュボードの Security Advisor で、現在どのテーブルがどのロールに公開されているかを確認します。テーブルエディターには「Data API exposure」バッジも表示されるようになっています。
SQL で直接確認することもできます。
select
grantee,
table_name,
string_agg(privilege_type, ', ' order by privilege_type) as privileges
from information_schema.table_privileges
where table_schema = 'public'
and grantee in ('anon', 'authenticated', 'service_role')
group by grantee, table_name
order by table_name, grantee;
Step 2:デフォルト権限を取り消す
既存プロジェクトで早期に移行する場合、まずデフォルトの自動付与を止めます。
-- 新しいテーブルへの自動 GRANT を停止
alter default privileges for role postgres in schema public
revoke select, insert, update, delete on tables
from anon, authenticated, service_role;
alter default privileges for role postgres in schema public
revoke execute on functions
from anon, authenticated, service_role;
alter default privileges for role postgres in schema public
revoke usage, select on sequences
from anon, authenticated, service_role;
このマイグレーションを適用すると、以降に作成するテーブルは明示的 GRANT がないと Data API に露出しなくなります。既存テーブルの権限はそのまま残ります。
Step 3:テーブルごとの GRANT をマイグレーションに追加
既存テーブルの権限を最小化したい場合は、現在の grant all を取り消してからテーブルごとに再付与します。
-- まず全部取り消す
revoke all on all tables in schema public
from anon, authenticated;
-- テーブルごとに再付与
grant select on public.posts to anon;
grant select, insert, update, delete on public.posts to authenticated;
grant select on public.profiles to anon;
grant select, update on public.profiles to authenticated;
-- 内部テーブルは authenticated にも公開しない
-- (service_role のみ、または RPC 関数経由でアクセス)
Step 4:ローカル開発環境を検証する
supabase db reset でマイグレーションを最初から再生し、アプリケーションが正常に動作することを確認します。GRANT がマイグレーションに含まれていれば、ローカルでも本番でも同じ権限構成が再現されます。
supabase db reset
npm run dev
# アプリの主要機能を手動テスト
Step 5:CI で権限の整合性を保証する
マイグレーションの diff に CREATE TABLE があるのに GRANT がない——これを CI で検知できると安心です。
#!/bin/bash
# pre-commit hook or CI step
for migration in supabase/migrations/*.sql; do
tables=$(grep -oP 'create table (?:if not exists )?public\.(\w+)' "$migration" | \
grep -oP 'public\.\w+')
for table in $tables; do
if ! grep -q "grant.*on ${table}" "$migration"; then
echo "WARNING: ${migration} creates ${table} but has no GRANT statement"
fi
done
done
2層セキュリティモデルの再確認
この変更を機に、Supabase のセキュリティモデルを整理しておきます。
Layer 1: GRANT → テーブルに「アクセスできるか」を制御
Layer 2: RLS → テーブルの「どの行を操作できるか」を制御
GRANT がなければ RLS の出番すらありません。逆に、GRANT があっても RLS が無効なら全行が見えます。両方セットで設計するのが正解です。
マイグレーションでは、テーブル作成・GRANT・RLS を1ファイルにまとめるのがベストプラクティスです。
-- 1ファイルに全部まとめる
create table public.comments (...);
grant select on public.comments to anon;
grant select, insert, update, delete on public.comments to authenticated;
alter table public.comments enable row level security;
create policy "..." on public.comments ...;
まとめ
| 項目 |
旧デフォルト |
新デフォルト |
| テーブル公開 |
自動 |
明示的 GRANT が必要 |
| GraphQL |
デフォルト有効 |
デフォルト無効 |
| 既存テーブル |
影響なし |
影響なし(新テーブルのみ) |
| 移行期限 |
— |
既存プロジェクトは 10月30日 |
やることは明確です。
- 今すぐ:Security Advisor で現状を監査する
- 5月30日まで:新規プロジェクトを作る予定があれば、GRANT パターンに慣れておく
- 10月30日まで:既存プロジェクトのマイグレーションに GRANT を追加する
「暗黙の全公開」から「明示的な最小権限」へ。地味な変更ですが、Supabase を本番で運用するなら避けて通れないアップデートです。マイグレーションファイルに権限が宣言的に残る——これはむしろ歓迎すべき変化だと思います。
参考リンク