skills/supabase-admin/SKILL.md
Supabase administration, RLS policies, migrations, and schema design. Use for database architecture, Row Level Security, performance tuning, auth integration. Activate on "Supabase", "RLS", "migration", "policy", "schema", "auth.uid()". NOT for Supabase Auth UI configuration (use dashboard), edge functions (use cloudflare-worker-dev), or general SQL without Supabase context.
npx skillsauth add curiositech/windags-skills supabase-adminInstall 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.
Master Supabase schema design, Row Level Security policies, migrations, and performance optimization for production applications.
Check data sensitivity & access patterns:
├── Public content (blogs, docs)
│ └── Use: Public read + owner write policies
├── User-owned data (profiles, settings)
│ └── Use: Owner-only access (auth.uid() = user_id)
├── Multi-tenant (org data)
│ ├── Small orgs (<1000 users): RLS with org_id filter
│ └── Large orgs (>1000 users): Schema per tenant
└── Admin content (system config)
└── Use: Role-based policies with profiles.role check
Evaluate data retention needs:
├── Needs audit trail or recovery?
│ ├── High compliance: Soft delete (deleted_at timestamp)
│ └── Medium compliance: Soft delete + cascade trigger
├── Performance critical with large volume?
│ ├── Yes: Hard delete with CASCADE
│ └── No: Soft delete acceptable
└── Referenced by other tables?
├── Critical refs: Use ON DELETE RESTRICT
└── Clean cascade: Use ON DELETE CASCADE
Check change impact:
├── Schema changes
│ ├── Adding nullable column: Low risk, deploy anytime
│ ├── Adding NOT NULL: Medium risk, needs backfill
│ └── Dropping column: High risk, needs staged deployment
├── RLS policy changes
│ ├── Relaxing permissions: Low risk
│ └── Restricting access: High risk, test with real data
└── Index changes
├── Creating index: Use CONCURRENTLY flag
└── Dropping index: Check query performance impact first
Symptoms: Tables with 20+ columns, nullable columns everywhere, poor query performance
Detection: SELECT count(*) FROM information_schema.columns WHERE table_name = 'bloated_table' returns >15
Fix: Normalize into related tables, use JSONB for flexible attributes, add NOT NULL constraints with defaults
Symptoms: Queries taking >1000ms, high CPU on simple selects, missing auth.uid() in WHERE clauses
Detection: EXPLAIN ANALYZE shows sequential scans on user_id columns without indexes
Fix: Add CREATE INDEX idx_table_user_id ON table(user_id) for every RLS-filtered column
Symptoms: Deployment timeouts, blocked writes during migration, "relation does not exist" errors
Detection: Migration contains ALTER TABLE ADD COLUMN x NOT NULL without DEFAULT
Fix: Split into: 1) Add nullable column with default, 2) Backfill data, 3) Add NOT NULL constraint
Symptoms: RLS policies calling auth.uid() for every row, poor performance on large tables
Detection: Policy has auth.uid() = user_id in USING clause without subquery
Fix: Rewrite as user_id = (SELECT auth.uid()) to parse JWT once per query
Symptoms: Data disappearing unexpectedly, orphaned records, referential integrity errors Detection: Foreign keys without explicit ON DELETE behavior, surprise empty result sets Fix: Explicitly set ON DELETE CASCADE/RESTRICT/SET NULL based on business logic
Scenario: SaaS app where users belong to organizations, need org-isolated data
Step 1: Schema Design Decision
-- Decision: Use org_id column vs separate schemas
-- Trade-off: RLS filtering vs complete isolation
-- Choice: RLS (easier ops, shared resources)
CREATE TABLE posts (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
org_id uuid NOT NULL REFERENCES organizations(id),
user_id uuid NOT NULL REFERENCES auth.users(id),
content text NOT NULL,
created_at timestamptz DEFAULT now()
);
Step 2: Index Strategy (Critical for Performance)
-- Compound index: org_id first (highest selectivity for RLS)
CREATE INDEX idx_posts_org_user ON posts(org_id, user_id);
-- Single index for user lookups
CREATE INDEX idx_posts_user ON posts(user_id);
Step 3: RLS Policy with Org Context
-- Get user's org_id from profiles table
CREATE POLICY "Org members only" ON posts
FOR ALL USING (
org_id = (
SELECT profiles.org_id
FROM profiles
WHERE profiles.id = auth.uid()
)
);
Step 4: Performance Validation
-- Test query performance
EXPLAIN ANALYZE
SELECT * FROM posts
WHERE org_id = 'test-org-id'
LIMIT 10;
-- Should show Index Scan, not Seq Scan
-- Should be <100ms for tables with <1M rows
Expert Insight: Novices miss the compound index ordering. They create (user_id, org_id) which forces RLS to scan all user posts then filter by org. Experts put org_id first since RLS filters by org membership.
RLS audit checklist - all must pass before production:
ALTER TABLE x ENABLE ROW LEVEL SECURITYSET ROLE anon and real user JWTsEXPLAIN ANALYZE shows Index Scan (not Seq Scan) for auth queriesALTER TABLE ADD COLUMN x NOT NULL without DEFAULT in single transactionDo NOT use this skill for:
cloudflare-worker-dev skill insteadpostgresql-dba skill for non-Supabase contextWhen to delegate:
postgresql-dba skilldocker-deployment or kubernetes-ops skillsdata-ai
license: Apache-2.0 NOT for unrelated tasks outside this domain.
development
Use when designing caching strategies (cache-aside, write-through, write-behind), implementing distributed locks, building rate limiters, leaderboards, real-time streams (XADD/consumer groups), pub/sub, or tuning eviction policies. Triggers: thundering-herd on cache miss, dogpile on key expiry, Redlock vs SET-NX-PX choice, sliding-window rate limiter, hot-key on a single cluster slot, big-key blowup, MULTI/EXEC across slots, KEYS in production. NOT for Redis Cluster operations/admin (different domain), embedded KV (SQLite, leveldb), in-process LRU caches, or Memcached.
tools
Drawing the `'use client'` boundary correctly in React Server Components apps (Next.js App Router, RSC frameworks) — leaf-pushing, slot composition, serialization rules, and environment poisoning prevention. Grounded in react.dev and Next.js 16 docs.
development
Use when designing rate limiting for an API, choosing between token bucket / sliding window / leaky bucket / fixed window, implementing it in Redis, deciding edge (Cloudflare/Upstash) vs origin enforcement, sizing per-user vs per-IP vs per-endpoint quotas, returning the right 429 response with Retry-After, or fixing the boundary-burst bug in fixed-window limiters. Triggers: 429 too many requests, INCR + EXPIRE, ZADD + ZREMRANGEBYSCORE + ZCARD, X-RateLimit-Remaining header, Cloudflare WAF rate limiting rules, Upstash @upstash/ratelimit, leaky bucket shaping vs policing, distributed rate limiter consistency. NOT for DDoS mitigation specifically (different scale), CAPTCHA / bot management, full WAF design, or per-user quota billing.