| name | supabase-migration |
| description | Supabase Migration 規範。Use when running supabase migration new, editing migration SQL files, creating database functions, or modifying database schema. Always use this skill when you see CREATE FUNCTION, ALTER TABLE, CREATE INDEX, or migration-related work. |
Supabase Migration 規範
Migration 核心規則已定義在 AGENTS.md(Local-First、MCP 禁止 DDL、search_path、不可變原則)。
本 skill 補充 AGENTS.md 未涵蓋的實作細節。
PR-isolated DB preview env / schema diff gate / production data sanitization 規約見 rules/core/db-preview-env.md(capability + safety contract)與 rules/modules/db-runtime/supabase-self-hosted/preview-env.md(self-host 實作層 + image trip-wires)。Cookbook 範本:vendor/snippets/db-preview-env/。
MCP 禁止執行 DDL
禁止使用以下 MCP 工具執行 DDL(CREATE / ALTER / DROP):
mcp__remote-supabase__apply_migration
mcp__remote-supabase__execute_sql
原因: MCP 使用 supabase_admin role 連線,透過它建立的 table/index/function 的 owner 是 supabase_admin 而非 postgres。當 CI/CD 用 migration 檔案部署時,postgres role 無法修改這些物件,導致部署失敗。
正確做法:
- 所有 DDL 透過
supabase migration new 建立 migration 檔案
- 透過 CI/CD pipeline 部署(owner =
postgres)
- Remote MCP 只能用於:SELECT 查詢、除錯、檢查 table owner
View 安全設定
所有 view 需設定 security_invoker:
CREATE OR REPLACE VIEW your_schema.my_view
WITH (security_invoker = true)
AS SELECT ...;
原因: Postgres 的 view 預設 bypass RLS(以 view owner 的權限執行)。不加 security_invoker = true 等於 RLS 對 view 無效。
SECURITY DEFINER 函式位置
NEVER 將 SECURITY DEFINER 函式放在 exposed schema(public):
CREATE FUNCTION public.dangerous_func() ... SECURITY DEFINER ...;
CREATE FUNCTION your_schema.safe_func() ... SECURITY DEFINER SET search_path = '' ...;
GRANT EXECUTE ON FUNCTION your_schema.safe_func TO authenticated;
若需要透過 PostgREST(Data API)呼叫,在 public 建立 thin wrapper(SECURITY INVOKER)呼叫 private schema 的實作。
開發流程
supabase migration new <description>
supabase db reset
supabase db lint --level warning
supabase db advisors
supabase gen types typescript --local | tee app/types/database.types.ts > /dev/null
pnpm typecheck
supabase db advisors 需 CLI v2.81.3+。若版本不足,可用 MCP get_advisors 替代。
Migration Timestamp Immutability(hard rule)
Migration 檔名的 timestamp prefix 是 supabase_migrations.version 的 identity key。Remote DB apply 後,該 timestamp 就是不可變的。
MUST
- 已 commit 進 main 且 remote DB 已 apply 的 migration NEVER rename / delete / regenerate timestamp
- Worktree 內跑
supabase db reset / supabase migration new 後 MUST 驗證既有 migration 檔名未變(ls supabase/migrations/ | head 比對 main)
- 發現 worktree 內 migration 檔名與 main 不同 → STOP,還原成 main 的檔名,不 commit 新 timestamp
NEVER
- NEVER 在 worktree 重新
supabase migration new <已存在的 migration 名> — 會產生新 timestamp 取代舊的
- NEVER 手動 rename migration 檔案的 timestamp prefix — 即使 SQL 內容完全相同
- NEVER 假設「local db:reset 過了 = remote 也會過」— local 從 scratch apply 不知道 remote 的
supabase_migrations 記錄
偵測
git diff --cached --diff-filter=R -- 'supabase/migrations/' | grep -q . && echo "BLOCK: migration renamed" || echo "OK"
為什麼
Supabase db push 比對 remote supabase_migrations.version 與 local supabase/migrations/ 的 timestamp prefix。Remote 有 20260604051558(已 apply)但 local 改成 20260605065826(同內容不同 timestamp)→ db push 報 Remote migration versions not found in local migrations directory → deploy 失敗。Local db:reset 從 scratch 永遠成功(不知道 remote state),是典型 dev-prod parity gap。
Reference: [[pitfall-migration-rename-breaks-remote-db-push]]
Production Resilience Classification
Self-host Supabase production migration 前,先分類 migration 風險:
| Classification | Examples | Required action |
|---|
online_safe | add nullable column, create table, create function v2, create index concurrently, add FK/check NOT VALID | 可在線上跑,但仍需 PostgREST /ready gate 與 smoke |
expand_contract_required | rename/drop column, change type, drop function signature, replace exposed RPC | 拆成 expand → app rollout → contract,不可一次 deploy |
maintenance_required | unavoidable ACCESS EXCLUSIVE, table rewrite, large blocking DML, non-concurrent hot-table index | hard stop;需 rollback/maintenance decision |
規則:
- NEVER 宣稱所有 migration 都能零停機。
- NEVER 把 app code retry/session fix 當成 migration safety。
- MUST 區分 PostgREST schema cache reload 與 DB lock;split surface 不能解除同一張 table 的 blocking lock。
- MUST 在 self-host production deploy 保存
/ready evidence、traffic smoke summary、PostgREST logs。
- 可用 clade script prototype(consumer sync 後位於
.codex/scripts/postgrest-resilience/):
.codex/scripts/postgrest-resilience/classify-migration.mjs <migration.sql>
.codex/scripts/postgrest-resilience/ready-watch.mjs --url=<admin-ready-url>
.codex/scripts/postgrest-resilience/smoke-runner.mjs --endpoint=<name=url>
Schema 規範
Schema 邊界
- core / auth: 授權相關(user_roles、allowed_emails、user_preferences)
- app / 專案名稱: 業務資料表
- public: 不存放業務資料,僅作 RPC 入口薄 wrapper
命名規則
- 表名:snake_case 複數(tool_inserts)
- 欄位:snake_case(created_at)
- 函式:snake_case(get_user_role)
- Enum:snake_case(user_role)
Sequence 同步
當使用 INSERT 直接指定 ID 匯入資料時,sequence 不會自動更新:
SELECT setval(
'your_schema.table_name_id_seq',
(SELECT COALESCE(MAX(id), 0) + 1 FROM your_schema.table_name),
false
);
參考資料
檢查清單