skills/stack/supabase-database-design/SKILL.md
# Supabase Database Design Patterns for designing clean, performant, and maintainable Postgres schemas in Supabase. Every table should be queryable through PostgREST and protectable with RLS. --- ## Table Naming Conventions - Use `snake_case` for all identifiers (tables, columns, indexes, constraints) - Table names are **plural**: `invoices`, `line_items`, `users` - Junction tables: `user_roles`, `project_members` (entity_relationship) - Prefix audit/system tables: `audit_logs`, `system_sett
npx skillsauth add 33prime/rtg-forge skills/stack/supabase-database-designInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Patterns for designing clean, performant, and maintainable Postgres schemas in Supabase. Every table should be queryable through PostgREST and protectable with RLS.
snake_case for all identifiers (tables, columns, indexes, constraints)invoices, line_items, usersuser_roles, project_members (entity_relationship)audit_logs, system_settingsEvery table MUST have these columns:
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
Every tenant-scoped table MUST include:
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE
And a composite index:
CREATE INDEX idx_<table>_tenant_id ON <table>(tenant_id);
| Use Case | Type | NOT This |
|---|---|---|
| Identifiers | UUID | SERIAL, BIGINT |
| Money/currency | NUMERIC(12,2) | FLOAT, DOUBLE PRECISION |
| Timestamps | TIMESTAMPTZ | TIMESTAMP (without tz) |
| Short strings | TEXT with CHECK(length(col) <= N) | VARCHAR(N) |
| Long text | TEXT | — |
| Enums | TEXT with CHECK constraint | Postgres ENUM type (hard to migrate) |
| Structured data | JSONB | JSON, TEXT |
| Booleans | BOOLEAN NOT NULL DEFAULT false | INTEGER |
| IP addresses | INET | TEXT |
-- YES — easy to change the constraint later
name TEXT NOT NULL CHECK(length(name) BETWEEN 1 AND 255)
-- AVOID — changing VARCHAR length requires ALTER COLUMN
name VARCHAR(255) NOT NULL
-- YES — easy to add new values
status TEXT NOT NULL DEFAULT 'draft' CHECK(status IN ('draft', 'sent', 'paid', 'cancelled'))
-- AVOID — adding values to ENUM requires ALTER TYPE
CREATE TYPE invoice_status AS ENUM ('draft', 'sent', 'paid', 'cancelled');
Every foreign key MUST specify ON DELETE behavior explicitly:
-- Parent owns children — delete cascades
customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE
-- Reference should not be deleted while referenced
category_id UUID NOT NULL REFERENCES categories(id) ON DELETE RESTRICT
-- Optional reference — set to null if parent deleted
assigned_to UUID REFERENCES users(id) ON DELETE SET NULL
ON DELETE — never rely on the default (NO ACTION)CASCADE when the child has no meaning without the parentRESTRICT when deletion should be preventedSET NULL for optional associationsWHERE clausesORDER BY with LIMIT-- Foreign key index
CREATE INDEX idx_invoices_customer_id ON invoices(customer_id);
-- Composite index for common query pattern
CREATE INDEX idx_invoices_tenant_status ON invoices(tenant_id, status);
-- Partial index for active records
CREATE INDEX idx_invoices_unpaid ON invoices(tenant_id, created_at)
WHERE status NOT IN ('paid', 'cancelled');
-- GIN index for JSONB queries
CREATE INDEX idx_invoices_metadata ON invoices USING GIN(metadata);
Use JSONB for truly dynamic/schemaless data. Do NOT use it to avoid schema design.
-- YES — truly dynamic metadata
metadata JSONB NOT NULL DEFAULT '{}'::jsonb
-- NO — this should be separate columns or a related table
data JSONB -- contains { "name": "...", "email": "...", "status": "..." }
Use views to simplify complex queries and provide clean PostgREST interfaces:
CREATE OR REPLACE VIEW invoice_summaries AS
SELECT
i.id,
i.tenant_id,
i.status,
i.created_at,
c.name AS customer_name,
COUNT(li.id) AS line_item_count,
COALESCE(SUM(li.quantity * li.unit_price), 0) AS total
FROM invoices i
JOIN customers c ON c.id = i.customer_id
LEFT JOIN line_items li ON li.invoice_id = i.id
GROUP BY i.id, c.name;
invoice_summaries, active_userssecurity_invoker = true for RLS-aware views (Postgres 15+)Every schema change goes through a numbered migration file.
supabase/migrations/
20260211000001_create_tenants.sql
20260211000002_create_users.sql
20260211000003_create_invoices.sql
IF NOT EXISTS for idempotent index/constraint creationCreate a reusable trigger function and apply to every table:
-- Create once
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Apply to each table
CREATE TRIGGER set_updated_at
BEFORE UPDATE ON invoices
FOR EACH ROW
EXECUTE FUNCTION update_updated_at();
development
# Parallel Execution > This skill is under development. Workflow patterns for running independent tasks in parallel to improve performance and throughput. ## Topics to Cover - Identifying independent tasks suitable for parallel execution - `asyncio.gather()` with `return_exceptions=True` - `asyncio.TaskGroup` for structured concurrency (Python 3.11+) - Semaphores for bounded concurrency - `Promise.all()` and `Promise.allSettled()` in TypeScript - Handling partial failures (some tasks succeed
development
# Module Extraction > This skill is under development. Workflow for identifying and extracting reusable modules from existing codebases. Extract when a pattern is used in 3+ places and has stabilized. ## Topics to Cover - Identifying extraction candidates (rule of three) - Defining module boundaries and public interface - Dependency analysis: what does the module need? - Interface design: protocols, abstract base classes - Step-by-step extraction process - Testing strategy: tests before, dur
development
# Forge Orchestrate — Intelligent Build Orchestration You are a build planner, not a build executor. Your job is to look at a project, figure out what's left to build, decompose the work into parallel streams, assign the right intelligence level to each stream, estimate cost, and hand the user a set of terminal commands they can run. You plan. They execute. --- ## Stream Decomposition The unit of parallelism is a **stream** — a self-contained bundle of tasks that one Claude session handles e
development
# Code Review > This skill is under development. Workflow for conducting effective code reviews that catch real issues and improve code quality. ## Topics to Cover - Review priorities: correctness > design > performance > style - What to check in every review (checklist) - How to give constructive feedback - Automated checks that should run before human review - Review scope: how big is too big? - Patterns for reviewing database migrations - Patterns for reviewing API changes - When to reque