database-migration

Guide for creating idempotent Supabase database migrations with RLS policies and workspace isolation

25 stars

Best use case

database-migration is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Guide for creating idempotent Supabase database migrations with RLS policies and workspace isolation

Teams using database-migration should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/database-migration/SKILL.md --create-dirs "https://raw.githubusercontent.com/ComeOnOliver/skillshub/main/skills/aiskillstore/marketplace/cleanexpo/database-migration/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/database-migration/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How database-migration Compares

Feature / Agentdatabase-migrationStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Guide for creating idempotent Supabase database migrations with RLS policies and workspace isolation

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Database Migration Skill
## Creating Idempotent Supabase Migrations

**When to Use**: Adding tables, modifying schemas, creating RLS policies, adding functions

---

## Process

### 1. Check Existing Schema
**ALWAYS** check before creating:
```bash
# Read schema reference
cat docs/guides/schema-reference.md

# Or check existing migrations
ls supabase/migrations/
```

### 2. Create Migration File

**Location**: `supabase/migrations/YYYYMMDDHHMMSS_description.sql`

**Naming**: Use timestamp + descriptive name
```
20251230120000_add_agent_registry_table.sql
```

### 3. Write Idempotent SQL

**Pattern**: Use `IF NOT EXISTS` and `CREATE OR REPLACE`

```sql
-- Tables
CREATE TABLE IF NOT EXISTS agent_registry (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
  agent_id TEXT NOT NULL,
  version TEXT NOT NULL,
  capabilities JSONB NOT NULL DEFAULT '[]'::jsonb,
  created_at TIMESTAMPTZ DEFAULT NOW(),

  UNIQUE(workspace_id, agent_id)
);

-- Indexes
CREATE INDEX IF NOT EXISTS idx_agent_registry_workspace
  ON agent_registry(workspace_id);

CREATE INDEX IF NOT EXISTS idx_agent_registry_agent
  ON agent_registry(agent_id, workspace_id);

-- RLS
ALTER TABLE agent_registry ENABLE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS "Users can view their workspace agents" ON agent_registry;
CREATE POLICY "Users can view their workspace agents" ON agent_registry
  FOR SELECT USING (
    workspace_id IN (
      SELECT w.id FROM workspaces w
      INNER JOIN user_organizations uo ON uo.org_id = w.org_id
      WHERE uo.user_id = auth.uid()
    )
  );

DROP POLICY IF EXISTS "System can manage agents" ON agent_registry;
CREATE POLICY "System can manage agents" ON agent_registry
  FOR ALL USING (true) WITH CHECK (true);

-- Functions
CREATE OR REPLACE FUNCTION get_agent_count(p_workspace_id UUID)
RETURNS INTEGER AS $$
BEGIN
  RETURN (SELECT COUNT(*) FROM agent_registry WHERE workspace_id = p_workspace_id);
END;
$$ LANGUAGE plpgsql STABLE;

-- Comments
COMMENT ON TABLE agent_registry IS 'Registry of all active agents per workspace';
```

### 4. RLS Policy Pattern

**ALWAYS use**: `user_organizations` + `workspaces` join (NOT workspace_members)

```sql
-- Correct pattern
workspace_id IN (
  SELECT w.id FROM workspaces w
  INNER JOIN user_organizations uo ON uo.org_id = w.org_id
  WHERE uo.user_id = auth.uid()
)

-- For admin/owner only
workspace_id IN (
  SELECT w.id FROM workspaces w
  INNER JOIN user_organizations uo ON uo.org_id = w.org_id
  WHERE uo.user_id = auth.uid() AND uo.role IN ('admin', 'owner')
)
```

### 5. Apply Migration

**Method**: Supabase Dashboard → SQL Editor

**Steps**:
1. Copy migration SQL
2. Paste into SQL Editor
3. Click "Run"
4. Verify success

**Alternative**: Use WORKING_MIGRATIONS.sql pattern for combined migrations

---

## Examples

### Example 1: Simple Table

```sql
CREATE TABLE IF NOT EXISTS my_table (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
  name TEXT NOT NULL,
  data JSONB,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_my_table_workspace ON my_table(workspace_id);

ALTER TABLE my_table ENABLE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS "workspace_isolation" ON my_table;
CREATE POLICY "workspace_isolation" ON my_table
  FOR ALL USING (
    workspace_id IN (
      SELECT w.id FROM workspaces w
      INNER JOIN user_organizations uo ON uo.org_id = w.org_id
      WHERE uo.user_id = auth.uid()
    )
  );
```

### Example 2: ENUM Type

```sql
DO $$ BEGIN
  CREATE TYPE agent_status AS ENUM ('active', 'paused', 'disabled');
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
```

### Example 3: Trigger

```sql
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
  NEW.updated_at = NOW();
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS trigger_update_updated_at ON my_table;
CREATE TRIGGER trigger_update_updated_at
  BEFORE UPDATE ON my_table
  FOR EACH ROW
  EXECUTE FUNCTION update_updated_at();
```

---

## Common Patterns

### Workspace Isolation (MANDATORY)

```sql
CREATE TABLE table_name (
  ...
  workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
  ...
);

-- Always add index on workspace_id
CREATE INDEX IF NOT EXISTS idx_table_workspace ON table_name(workspace_id);

-- Always add RLS
ALTER TABLE table_name ENABLE ROW LEVEL SECURITY;
```

### Constraints

```sql
-- Check constraints
CONSTRAINT valid_status CHECK (status IN ('active', 'inactive')),
CONSTRAINT valid_score CHECK (score >= 0 AND score <= 100),
CONSTRAINT valid_email CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$')
```

### Foreign Keys

```sql
-- With cascade delete
workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,

-- With set null
created_by UUID REFERENCES auth.users(id) ON DELETE SET NULL,

-- With restrict (prevents delete if referenced)
org_id UUID NOT NULL REFERENCES organizations(id) ON DELETE RESTRICT
```

---

## Checklist

Before applying migration:

- [ ] Checked schema-reference.md for conflicts
- [ ] Used `IF NOT EXISTS` on tables
- [ ] Used `CREATE OR REPLACE` on functions
- [ ] Added workspace_id column (if multi-tenant table)
- [ ] Created index on workspace_id
- [ ] Enabled RLS
- [ ] Added RLS policies (user + system)
- [ ] Used correct RLS pattern (user_organizations join)
- [ ] Added constraints where appropriate
- [ ] Added comments for documentation
- [ ] Tested SQL syntax locally

---

## Troubleshooting

**Error**: "relation workspace_members does not exist"
**Fix**: Use `user_organizations` + `workspaces` join (see RLS pattern above)

**Error**: "already exists"
**Fix**: Use `IF NOT EXISTS` or `CREATE OR REPLACE`

**Error**: "permission denied"
**Fix**: Use service role key in Supabase Dashboard, not anon key

---

**Standard**: Idempotent, workspace-isolated, RLS-secured, well-documented

Related Skills

winui3-migration-guide

25
from ComeOnOliver/skillshub

UWP-to-WinUI 3 migration reference. Maps legacy UWP APIs to correct Windows App SDK equivalents with before/after code snippets. Covers namespace changes, threading (CoreDispatcher to DispatcherQueue), windowing (CoreWindow to AppWindow), dialogs, pickers, sharing, printing, background tasks, and the most common Copilot code generation mistakes.

scaffolding-oracle-to-postgres-migration-test-project

25
from ComeOnOliver/skillshub

Scaffolds an xUnit integration test project for validating Oracle-to-PostgreSQL database migration behavior in .NET solutions. Creates the test project, transaction-rollback base class, and seed data manager. Use when setting up test infrastructure before writing migration integration tests, or when a test project is needed for Oracle-to-PostgreSQL validation.

reviewing-oracle-to-postgres-migration

25
from ComeOnOliver/skillshub

Identifies Oracle-to-PostgreSQL migration risks by cross-referencing code against known behavioral differences (empty strings, refcursors, type coercion, sorting, timestamps, concurrent transactions, etc.). Use when planning a database migration, reviewing migration artifacts, or validating that integration tests cover Oracle/PostgreSQL differences.

planning-oracle-to-postgres-migration-integration-testing

25
from ComeOnOliver/skillshub

Creates an integration testing plan for .NET data access artifacts during Oracle-to-PostgreSQL database migrations. Analyzes a single project to identify repositories, DAOs, and service layers that interact with the database, then produces a structured testing plan. Use when planning integration test coverage for a migrated project, identifying which data access methods need tests, or preparing for Oracle-to-PostgreSQL migration validation.

issue-fields-migration

25
from ComeOnOliver/skillshub

Bulk-migrate metadata to GitHub issue fields from two sources: repo labels (e.g. priority labels to a Priority field) and Project V2 fields. Use when users say "migrate my labels to issue fields", "migrate project fields to issue fields", "convert labels to issue fields", "copy project field values to issue fields", or ask about adopting issue fields. Issue fields are org-level typed metadata (single select, text, number, date) that replace label-based workarounds with structured, searchable, cross-repo fields.

creating-oracle-to-postgres-migration-integration-tests

25
from ComeOnOliver/skillshub

Creates integration test cases for .NET data access artifacts during Oracle-to-PostgreSQL database migrations. Generates DB-agnostic xUnit tests with deterministic seed data that validate behavior consistency across both database systems. Use when creating integration tests for a migrated project, generating test coverage for data access layers, or writing Oracle-to-PostgreSQL migration validation tests.

creating-oracle-to-postgres-migration-bug-report

25
from ComeOnOliver/skillshub

Creates structured bug reports for defects found during Oracle-to-PostgreSQL migration. Use when documenting behavioral differences between Oracle and PostgreSQL as actionable bug reports with severity, root cause, and remediation steps.

creating-oracle-to-postgres-master-migration-plan

25
from ComeOnOliver/skillshub

Discovers all projects in a .NET solution, classifies each for Oracle-to-PostgreSQL migration eligibility, and produces a persistent master migration plan. Use when starting a multi-project Oracle-to-PostgreSQL migration, creating a migration inventory, or assessing which .NET projects contain Oracle dependencies.

database-query

25
from ComeOnOliver/skillshub

Query database safely with parameterized statements

react-native-brownfield-migration

25
from ComeOnOliver/skillshub

Provides an incremental adoption strategy to migrate native iOS or Android apps to React Native or Expo using @callstack/react-native-brownfield for initial setup. Use when planning migration steps, packaging XCFramework/AAR artifacts, and integrating them into host apps.

migration-architect

25
from ComeOnOliver/skillshub

Migration Architect

database-designer

25
from ComeOnOliver/skillshub

Use when the user asks to design database schemas, plan data migrations, optimize queries, choose between SQL and NoSQL, or model data relationships.