# Supabase Safe Migration

> Repository-specific workflow for Supabase/PostgreSQL changes, including migrations, tables, columns, constraints, indexes, RLS, RPC, triggers, SQL helpers, seed data, Storage policies, integration tests, race conditions, and database-dependent business behavior. Use before changing or relying on database behavior.

- Skill: `khangnhoang/supabase-safe-migration` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add khangnhoang/supabase-safe-migration`
- Raw SKILL.md: https://api.skillmd.com/api/skills/khangnhoang/supabase-safe-migration/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Data & Analytics
- Author: khangnhoang (https://skillmd.com/u/khangnhoang)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/khangnhoang/supabase-safe-migration

---


# Supabase Safe Migration

## Activation scope

Use this skill when a task touches:

* tables, columns, enums, constraints, or indexes
* migrations or schema drift
* RLS policies or permission helpers
* RPC functions, triggers, or SQL helpers
* seed data
* Supabase Storage policies
* database-backed integration tests
* race-condition-sensitive state
* Supabase CLI commands such as `db reset`, `db diff`, `db push`, or migration repair

Do not use it for pure UI work with no database dependency.

Database behavior changes must be represented by migrations, not ad hoc dashboard SQL.

Never push migrations or modify a remote database unless the user explicitly requests it.

## Related skills

Use:

* `nextjs-server-action-zod` when the requested work changes or reviews a Server Action, Route Handler, RPC argument/payload contract, form, schema, or DTO; inspecting an unchanged caller or supplied RPC payload only as migration evidence does not activate it
* `test-quality-strategy` for database, RLS, RPC, migration, trigger, and concurrency coverage
* `frontend-workflow` when seed data or fixtures are intended for browser or frontend QA, even when no UI code changes
* `code-commenting-and-maintainability` for non-obvious SQL and database-boundary comments
* `implementation-planning-and-pr-breakdown` for multi-step migration or dependency ordering
* `git-checkpoint-workflow` for local checkpoint commits

Read all relevant skills before editing.

## Resource routing

| Resource | Read condition | Skip when |
| --- | --- | --- |
| [references/migration-and-seed.md](references/migration-and-seed.md) | Read before adding/reviewing a migration, schema/table/column/index/constraint/backfill, or seed change | RLS/RPC/trigger/Storage investigation with no migration/seed review or change |
| [references/rls-and-storage.md](references/rls-and-storage.md) | Read before changing/reviewing RLS policies, permission helpers, bucket access, or Storage policies | Schema-only/RPC-only/trigger-only work, including an event or trigger that merely enables RLS without reviewing or changing an RLS policy, permission helper, or Storage access |
| [references/rpc-trigger-concurrency.md](references/rpc-trigger-concurrency.md) | Read when the requested outcome explicitly adds, changes, or reviews an RPC, trigger, SQL helper, race-sensitive transition, lock, retry, or idempotency behavior | General migration/constraint work where those objects appear only inside supplied SQL or tests; additive schema/index/seed work without a requested behavior target |

Read every reference whose condition matches the outcome or invariant the task explicitly asks to change or review. For RPC routing, a request merely to review or locally verify a supplied migration remains general migration scope: do not activate `rpc-trigger-concurrency.md` because its SQL creates or replaces an RPC or contains lock or concurrency implementation details. Read that reference when the request or owner-stated invariant explicitly names the RPC, trigger, concurrency, locking, retry, or idempotency behavior to add, change, or review. A remote-push-only request without a procedure trigger uses the core permission stop.

## Core rules

* Read existing migrations, schema objects, policies, helpers, triggers, RPCs, tests, seed data, and call sites first.
* Make the smallest focused database change.
* Create a new migration unless the user explicitly asks to amend unpublished local work.
* Do not edit old published migrations.
* Do not weaken RLS or constraints to make code or tests pass.
* Reuse existing helpers and project patterns.
* Treat RLS as a security boundary and constraints as final integrity enforcement.
* Do not rely only on TypeScript or client validation for database invariants.
* Prefer additive, existing-data-safe steps.
* Before deciding an ordered soft-delete backfill or constraint change, identify active display ordering, deleted-state retention, and restore conflict semantics from authoritative ADRs, mutation paths, and tests. Keep these domains separate unless the product contract explicitly joins them; stop when their contract or intended order cannot be established.
* Keep locks and transactions short.
* Do not call external services while holding database locks.
* Preserve idempotency for retryable payment, webhook, and status-transition logic.
* Surface schema, permission, or data-model conflicts before editing.
* Do not hide business-rule changes inside SQL without reviewing schemas, actions, tests, and seed assumptions.
* Do not modify unrelated database objects.
* Never run `db push` without explicit permission.

## Specialist escalation signals

A hard-risk signal exists when observable database facts expose a potentially material unresolved uncertainty about an RLS or permission boundary; `SECURITY DEFINER` or `search_path`; a destructive or compatibility-sensitive migration, backfill, or constraint on existing data; a transaction, lock, concurrency, or idempotency invariant; a trigger side effect; or a permission-sensitive RPC. Group signals by the threatened data or authorization invariant.

Additive migrations with known compatibility, mechanical generated-type updates, reuse of an established policy or RPC pattern, and ordinary local reset or drift checks are conditional review signals unless evidence exposes unresolved material risk. Documentation-only changes, file count, Supabase activation, or a request to double-check are ordinary non-triggers.

Route a hard-risk candidate through the global specialist gates only after applicable main review. Existing migration, RLS, remote-database, destructive-action, verification, and stop rules remain authoritative; no signal grants database, implementation, or remote permission.

## Required workflow

### Before editing

1. Identify the affected database behavior and business invariant.
2. Inspect relevant migrations and current schema objects.
3. Inspect related:
   * tables and relationships
   * constraints and indexes
   * RLS policies and helper functions
   * RPCs, triggers, and SQL helpers
   * Storage policies
   * seed data
   * TypeScript/Zod/action/handler call sites
   * integration tests
4. Search for an existing pattern or helper that already fits.
5. Decide whether the task needs:
   * a migration
   * seed changes
   * schema/type or call-site changes
   * RLS/RPC/trigger tests
   * local reset or drift checks
6. Surface conflicts before writing SQL.

### While editing

* Keep one migration focused on the approved behavior.
* Preserve naming and helper patterns.
* Apply changes in an existing-data-safe order.
* Keep permission checks and invariants explicit.
* Update application call sites only when required.
* Add concise Vietnamese comments only for non-obvious ordering, RLS, atomicity, lock, trigger, backfill, or rollback reasoning.
* Avoid unrelated refactors.

### After editing

* Apply the migration locally.
* Check drift when relevant.
* Run the smallest relevant test set.
* Confirm RLS/helper/trigger patterns were reused or intentionally changed.
* Confirm no unrelated schema object changed.
* Report exact changes, commands, results, skipped checks, and pending manual QA.

## Permission helper rules

Use explicit restrictive behavior when hard delete must be blocked.

Do not create a new permission helper until existing helpers are proven insufficient and the new boundary is approved.

## Trigger scope

Do not:

* create another updated-at helper unnecessarily
* modify managed schemas such as `auth`, `storage`, `cron`, or `realtime` without explicit scope
* touch auth provisioning triggers unless the task is specifically about provisioning

## Storage safety

Do not broaden Storage access casually.

## RPC rules

Use RPC when an operation must be atomic, permission-sensitive, or concurrency-safe at the database layer.

Before creating or changing one:

1. Inspect existing RPC and helper patterns.
2. Decide whether `SECURITY DEFINER` is necessary.
3. Set a safe `search_path` when using `SECURITY DEFINER`.
4. Check actor permission and current state explicitly.
5. Make retryable transitions idempotent.
6. Avoid duplicate side effects.
7. Return only required data.
8. Update callers and tests when needed.

Do not create RPC merely to hide ordinary CRUD.

## Database-specific comments

Follow `code-commenting-and-maintainability`.

Comment only non-obvious:

* safe migration order
* existing-data backfill
* RLS boundary
* trigger/helper purpose
* lock or atomic update
* partial constraint/index
* `SECURITY DEFINER` or `search_path`
* retry/idempotency invariant
* rollback-sensitive behavior

Good:

```sql
-- Backfill trước khi thêm NOT NULL để migration chạy được trên database đã có dữ liệu.
```

Do not narrate ordinary SQL syntax.

## Verification matrix

Choose the smallest relevant set.

Use the matching verification details in the selected references.

Inspect `package.json` and Supabase config before choosing exact commands.

Typical commands may include:

```bash
npx supabase db reset
npx supabase db diff --schema public
npm run test:integration
npm run lint
npm run typecheck
```

Do not invent unavailable scripts.

A DB-backed integration change is not complete until the local database can be rebuilt successfully.

When smoke E2E uses an isolated Supabase workdir/runtime, migrations and RPCs needed by E2E must be applied there too, not only to the root local Supabase database. If E2E reports a missing function after migration work, check both the root DB and the E2E DB/workdir; reset the E2E workdir locally when it is stale, then restart PostgREST/schema cache only after confirming the function exists. Never apply this troubleshooting flow to production without explicit owner approval.

Never run `npx supabase db push` without explicit user permission.

## Anti-patterns

Do not:

* edit published migrations
* use dashboard SQL as the final change
* weaken RLS or constraints
* duplicate permission helpers
* create redundant updated-at functions
* trust client-provided owner, role, status, price, payment, or counter fields
* hold locks during external calls
* make retryable operations non-idempotent
* skip reset after migration changes
* ignore unexplained schema drift
* change unrelated tables, policies, or triggers
* claim remote application when only local checks ran

## Reporting

In the response, name selected and skipped bundled references and briefly tie the selection to their read conditions. For a core-only task, state that no conditional reference applies.

For each applicable database boundary under review, state the conclusion, the supporting evidence, any remaining unknown, and the smallest verification still needed. Distinguish checks that ran from checks that are only planned, and state why a boundary is not applicable instead of silently omitting it.

## Final checklist

* [ ] Existing DB patterns and call sites were inspected
* [ ] A focused new migration represents the behavior
* [ ] Existing data remains valid
* [ ] RLS and constraints preserve intended boundaries
* [ ] RPC/trigger/lock behavior is justified
* [ ] Retryable behavior is idempotent
* [ ] Relevant application contracts remain compatible
* [ ] Reset and relevant tests passed
* [ ] Drift was checked when needed
* [ ] No unrelated DB object changed
* [ ] Comments explain only non-obvious decisions
* [ ] Remote DB was not modified without explicit permission
* [ ] Exact verification and limitations were reported

