Help us improve
Share bugs, ideas, or general feedback.
From opinionated-prisma
Prisma schema design conventions: naming, primary keys, enums, relations, soft delete. Use when designing models, adding fields, choosing between enums and strings, or setting up a new Prisma schema. Triggers on "schema", "model", "relation", "enum", "field", "prisma.schema", "@@map", "@map", "uuid", "slug".
npx claudepluginhub esot321c/opinionated-prismaHow this skill is triggered — by the user, by Claude, or both
Slash command
/opinionated-prisma:schema-designThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
- Always `@@map` models to snake_case table names, `@map` fields to snake_case columns.
Guides Payload CMS config (payload.config.ts), collections, fields, hooks, access control, APIs. Debugs validation errors, security, relationships, queries, transactions, hook behavior.
Builds scalable data pipelines, modern data warehouses, and real-time streaming architectures using Apache Spark, dbt, Airflow, and cloud-native platforms.
Share bugs, ideas, or general feedback.
@@map models to snake_case table names, @map fields to snake_case columns.@@index on foreign key fields. Prisma does not create these automatically.@default(dbgenerated("uuid_generate_v7()")) with @db.Uuid for all primary keys. Do not use @default(uuid()) which generates v4 (random, poor index locality). Do not use CUID or ULID as primary keys (string storage, 26-37 bytes vs 16 bytes for native uuid, no Postgres uuid operations).slug String @unique field. The primary key is an internal concern; display identifiers are a separate field.String with application-level validation over Prisma/Postgres enums for most status and category fields. Database enums require a migration to add values; string fields don't.Json type unless the structure is genuinely dynamic and unqueried (e.g. raw webhook payloads, third-party metadata blobs).createdAt and updatedAt with @map to snake_case.deletedAt DateTime? not a boolean flag.Use database enums when:
entryType where INVOICE requires a lineItems relation and RECEIPT requires an attachments relation).Use string fields when:
model Client {
id String @id @default(dbgenerated("uuid_generate_v7()")) @db.Uuid
email String @unique
slug String @unique // "parr-business-law"
name String
matters Matter[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@map("clients")
}
model Matter {
id String @id @default(dbgenerated("uuid_generate_v7()")) @db.Uuid
clientId String @map("client_id") @db.Uuid
client Client @relation(fields: [clientId], references: [id])
title String
status String @default("open")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@index([clientId])
@@map("matters")
}
model matter {
id String @id @default(uuid())
client_id String
client Client @relation(fields: [client_id], references: [id])
data Json // storing structured matter details as JSON blob
status String @default("open") // stringly-typed with no app-level validation
createdAt DateTime @default(now())
// no @@map, no @@index on FK, no updatedAt, uuid v4
}
// Database enum for values that will change with business requirements
enum MatterStatus {
OPEN
CLOSED
ON_HOLD
// Need to add UNDER_REVIEW? That's a migration and deploy.
}
Problems: no @@map so Prisma uses model name as table name with inconsistent casing. No @@index on FK so every join scans the table. uuid() gives v4 (random, poor index locality). Json field for data that should be relational. Missing updatedAt. Database enum for a status that will evolve.