Overview

schema.prisma is the single source of truth for your database shape. It defines the datasource (which database to connect to), the generator (what client to emit), and every model (table), field (column), and relation (foreign key). Prisma reads this file to generate migrations and the typed client. Treat it the way you treat compiled code: the schema is the contract; everything else is derived.

Declare one datasource and pin the provider

A schema.prisma file supports exactly one datasource block.

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

Pin the provider to postgresql, mysql, or sqlite. Do not switch providers without regenerating all migrations from scratch. Provider determines which SQL dialect Prisma emits and which native types are available. Use env() for the URL so the secret never lands in source control. See postgres for connection string format.

Configure the generator for your runtime

The generator block controls what Prisma emits after prisma generate.

generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["driverAdapters"]
  output          = "../node_modules/.prisma/client"
}
  • Use prisma-client-js for Node and Bun.
  • Add previewFeatures only when you need a specific capability (driver adapters, metrics). Remove them when the feature reaches GA.
  • Set output when the default node_modules path conflicts with a monorepo layout. See prisma-client for the singleton pattern.

Give every model an @id field

Prisma requires exactly one @id or @@id per model.

model Product {
  id        String   @id @default(cuid())
  sku       String   @unique
  price     Int
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}
  • Prefer cuid() or uuid() over autoincrement() when you distribute writes across multiple databases or replicas.
  • Use @updatedAt for timestamps Prisma should maintain automatically. It sets the field to now() on every update call.
  • Use @default(now()) for createdAt. Never compute it in application code.

Declare @unique and @@unique for natural keys

Unique constraints live in the schema, not in a migration file you wrote by hand.

model User {
  id    String @id @default(cuid())
  email String @unique
 
  @@unique([tenantId, slug])
}
  • Put @unique on single-field natural keys (email, slug, external ID).
  • Use @@unique([a, b]) for composite uniqueness. Prisma generates the constraint name automatically.
  • Use @@index([col]) for columns you filter or sort on but do not need unique. See postgres for index sizing rules.

Define relations explicitly on both sides

Prisma requires that you declare the full relation on at least one model.

model Order {
  id     String @id @default(cuid())
  userId String
  user   User   @relation(fields: [userId], references: [id], onDelete: Cascade)
  items  OrderItem[]
}
 
model User {
  id     String  @id @default(cuid())
  orders Order[]
}
  • Specify fields (the foreign key column), references (the referenced column), and onDelete behavior.
  • Cascade deletes children when the parent is deleted. Restrict (the Prisma default) blocks the parent deletion. Choose explicitly rather than relying on defaults.
  • For self-relations, declare both the scalar and the relation field on the same model.

Set relationMode when your database does not enforce foreign keys

Some hosted databases (PlanetScale, older MySQL, read replicas) do not enforce foreign-key constraints at the engine level.

datasource db {
  provider     = "mysql"
  url          = env("DATABASE_URL")
  relationMode = "prisma"
}
  • "prisma" emulates foreign-key checks in the Prisma query layer instead of at the DB level.
  • "foreignKeys" (default) relies on the database to enforce constraints. Use this with postgres.
  • Mixing "prisma" mode with Postgres is legal but redundant. Pick one enforcement layer.

Map Prisma scalar types to native database types

Prisma provides cross-DB scalar types. Use @db.* native type modifiers when the default mapping is too broad.

model Article {
  id      String @id @default(cuid())
  title   String @db.VarChar(255)
  body    String @db.Text
  score   Float  @db.DoublePrecision
  meta    Json
  enabled Boolean @default(true)
}
  • String maps to TEXT by default in Postgres. Use @db.VarChar(n) when length matters for indexes or storage.
  • Int maps to INTEGER. Use @db.BigInt for columns that may exceed 2 billion.
  • Json maps to JSONB in Postgres. Queries on Json fields require $queryRaw. See prisma-raw-queries.
  • After changing native type modifiers, run prisma migrate dev to emit the correct ALTER TABLE. See prisma-migrations.