open-weight/skills/postgres-schema-design/SKILL.md
Use when designing or modifying a PostgreSQL database schema, adding tables or columns, creating indexes, or making any structural database change. This project uses Prisma.
npx skillsauth add jon23d/skillz postgres-schema-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.
All schema changes go through migrations. Never apply raw DDL directly to the database.
The migration tool is the source of truth for schema state.
Look for prisma/schema.prisma. If it does not exist, ask before proceeding.
npx prisma init (creates prisma/schema.prisma and .env)postgresqlnpx prisma migrate dev --name initprisma/schema.prisma + prisma/migrations/ folderprisma/schema.prisma — models, fields, relations, indexes, constraintsnpx prisma migrate dev --name <descriptive_name> (e.g. add_teams, add_stripe_customer_id_to_orgs)prisma/migrations/<timestamp>_<name>/migration.sql — check for unintended DROP statements, correct ON DELETE clauses, FK indexesprisma/schema.prisma and the migration folder togethernpx prisma migrate deploy && npx prisma generateUUID primary key:
id String @id @default(uuid())
Timestamps (audit columns):
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
FK with explicit delete behavior:
organizationId String @map("organization_id")
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
Index on FK:
@@index([organizationId])
Unique constraint:
@@unique([organizationId, email])
Enum:
enum SubscriptionStatus {
TRIALING
ACTIVE
PAST_DUE
CANCELED
PAUSED
}
Explicit join table (preferred over implicit many-to-many):
model TeamMember {
teamId String @map("team_id")
userId String @map("user_id")
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) @map("created_at")
@@id([teamId, userId])
@@index([userId])
@@map("team_members")
}
migration.sql — Prisma tracks state via checksumprisma db push in production or development (unless prototyping with accepted DB reset)SERIAL/BIGSERIAL for PKsUUID (gen_random_uuid() / @default(uuid())) — not SERIAL/BIGSERIALTIMESTAMPTZ — not TIMESTAMP (always UTC)ENUM type — not VARCHAR + app validationJSONB — not TEXT storing JSONVARCHAR(n) when max length is meaningfulNUMERIC(precision, scale) — never FLOAT/REALBOOLEAN NOT NULL DEFAULT false — not integer flagsusers, organizations, audit_logs<referenced_table_singular>_id — organization_id, user_idis_/has_ prefix — is_active, has_verified_email_at suffix — created_at, updated_at, deleted_atidx_<table>_<columns> — idx_users_organization_iduq_<table>_<columns>fk_<table>_<column>chk_<table>_<description>created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ -- soft delete; NULL means active
ON DELETE clause: CASCADE (child meaningless without parent), SET NULL (optional relationship), RESTRICT (guarded deletion)NOT NULL by default; nullable only when NULL has distinct meaningCHECK constraints for bounded values (e.g. CHECK (amount > 0))UNIQUE at DB level, not just application codeUNIQUE INDEX for natural keys (email, slug)WHERE deleted_at IS NULL)organization_id FKorganization_id on every scoped tableALTER TABLE users ENABLE ROW LEVEL SECURITY;
CREATE POLICY users_tenant_isolation ON users
USING (organization_id = current_setting('app.current_org_id')::uuid);
WHERE deleted_at IS NULLCREATE INDEX idx_<table>_active ON <table> (id) WHERE deleted_at IS NULLschema.sql canonical file — it drifts.sql files unless explicitly using Flyway/raw SQL runnerpsql — generate migration first.sql file outside the migrations folder → use the frameworkCREATE TABLE in a scratch SQL block → use the schema fileprisma generate not done → client out of syncdevelopment
Use when adding or modifying environment variable handling in TypeScript projects or monorepos — especially when using process.env directly, missing startup validation, sharing env schemas across packages, or encountering "undefined is not a string" errors at runtime from missing env vars.
testing
Use when creating a new skill, editing an existing skill, writing a SKILL.md, or verifying a skill works before deployment.
development
React UI design principles and conventions. Load when building or modifying any user interface or React components. Covers application type detection, visual standards, component design and structure, Mantine (business apps) and Tailwind (consumer apps), accessibility, responsiveness, state management, data fetching, testing, and in-app help patterns.
development
Use when setting up ESLint and/or Prettier in a TypeScript project, adding linting to an existing TypeScript codebase, or configuring typescript-eslint, eslint-config-prettier, or related packages.