Skip to main content
Prisma Documentation Docs

Search documentation

Type to search this documentation.

On this pageOverview

Overview

Data modeling is the process of describing the data your application needs and how that data is connected.

For example, a blog has users, posts, and comments. A user has fields like an email and a name. A post has fields like a title and content. These models also relate to each other: a user can write many posts, and a post can have many comments.

In Prisma ORM, you define this structure in your contract, the contract.prisma file that replaced schema.prisma. It holds your models the way schema.prisma did, and Prisma ORM derives both your TypeScript types and your migrations from it.

There is no datasource or generator block any more, because the connection string has moved to prisma.config.ts. A complete contract shows a whole file together with the config file, and if you are coming from Prisma ORM 7, that page covers the rename and the rest of the differences.

Prisma ORM 8 supports PostgreSQL and MongoDB today, and Supported databases lists the ones that are coming. Whichever you use, a contract is built from the same four building blocks:

All four blocks apply to both databases. How you model relations is where the two differ, so continue with relational data modeling or MongoDB data modeling.

A model describes one kind of record: a user, an order, a blog post. Declare a model with the model keyword and give it fields:

model User {

  id    Int    @id @default(autoincrement())

  email String

  name  String?

}

On a relational database, a model becomes a table. On MongoDB, it becomes a collection.

A primary key is the field that uniquely identifies each record. Give every model one and mark it with @id, because update and delete fail at runtime on a model that has neither a primary key nor a unique field:

model User {

  id    Int    @id @default(autoincrement())

  email String

}
prisma
model User {
  id    ObjectId @id @map("_id")
  email String
  @@map("users")
}

On MongoDB, the primary key is the document's mandatory _id field, and it must be an ObjectId. @map("_id") gives the field that name in the database. The field takes no @default, because MongoDB assigns the id itself when you create a document without one. @@map("users") names the collection and is optional: without it, the collection is the model name exactly as written, User.

On MongoDB, the primary key is the document's mandatory _id field, and it must be an ObjectId. @map("_id") gives the field that name in the database. The field takes no @default, because MongoDB assigns the id itself when you create a document without one. @@map("users") names the collection and is optional: without it, the collection is the model name exactly as written, User.

A natural key is a value that already identifies the record in the real world. Use one when the value is stable, unique, and assigned outside your application, such as a standardized code you receive rather than invent.

Natural keys fit reference tables best, the tables that hold a fixed set of values other records point at. A country, for example, is identified by its ISO code, and that code never changes:

model Country {

  code String @id

  name String

}

Records that point at Country now store a readable value (US, DE) instead of a meaningless number. Currencies (USD, EUR) and other reference tables work the same way.

A surrogate key is a generated value with no business meaning: an auto-incrementing integer, a UUID, an ObjectId. It is the better default for records your application creates, because it is stable. Values you might be tempted to use as a key, like an email address or a product SKU, change in practice, and changing a primary key is expensive because every record that points at the old value must be updated too. A surrogate key never changes.

Keep the natural value as a regular field and enforce its uniqueness with @unique, so you get a stable key and the uniqueness guarantee at the same time:

model User {

  id    Int    @id @default(autoincrement())

  email String @unique

}

Pick by how the record is created and where its id travels:

// Auto-incrementing integer: smallest and fastest to index.

// Good for internal records whose id never leaves your system.

model Invoice {

  id Int @id @default(autoincrement())

}

The database assigns the value, so you only know it after the insert. It is also sequential, so it leaks row counts and invites guessing if you expose it in URLs.

// UUID: globally unique, generated before the insert.

// Good for ids that appear in URLs or are created across services.

model ApiToken {

  id String @id @default(uuid())

}

A UUID takes more space than an integer, but independent services never produce the same one. @default(uuid()) generates a random UUID, and @default(uuid(7)) generates one that sorts by creation time. On MongoDB there is no choice to make, because the key is always the ObjectId shown above.

A composite key spans several fields, declared with @@id. Use one when the identity really is the combination, which usually means a model that links two others:

model UserTag {

  userId Int

  tagId  Int

  @@id([userId, tagId])

}

A duplicate (userId, tagId) pair would be meaningless, so the pair itself is the key. Many-to-many writes a model like this one out with its relation fields. Avoid composite keys for ordinary models, though, because everything that points at the model then has to store all of the key's fields.

A scalar field holds a single value. The common types:

Type Stores
String Text
Int 32-bit integer
BigInt 64-bit integer
Float Floating-point number
Decimal, or Numeric(10, 2) with a precision and scale Exact decimal, PostgreSQL numeric
Boolean true / false
DateTime Timestamp, PostgreSQL timestamptz
Bytes Binary data, PostgreSQL bytea
Json Arbitrary JSON value, PostgreSQL's native json
Jsonb Arbitrary JSON value, PostgreSQL's jsonb
ObjectId MongoDB document identifier

The PostgreSQL names in the table are the column types Prisma ORM creates. ObjectId is a MongoDB-only type, and MongoDB names its other scalar types after the BSON types they store, such as Int32 and Bool; MongoDB data modeling lists them.

A Decimal reaches your code as a string, so nothing is rounded through a JavaScript number, and a Bytes value reaches you as a Uint8Array. On PostgreSQL, a DateTime reaches you as a Temporal.Instant, the standard JavaScript type for a point in time. Temporal is built into Node.js 26.8.2 and later, but Homebrew's build of Node.js 26 leaves it out, so check with node -p "typeof Temporal". If that prints undefined, install the temporal-polyfill package and add import "temporal-polyfill/full/global" to your app's entry file, before any query runs. That import covers your app only, and contract emit needs its own Temporal when a date or time field has a fixed default, as Default values explains. To work with a JavaScript Date on PostgreSQL instead, and need no polyfill, type the field TimestamptzJsDate, but a Date drops the microseconds PostgreSQL stores.

Json and Jsonb are two separate types, and they differ in how you can filter on them. On a Jsonb field, .where() compares the whole value, so you can ask whether the column equals a given document but not whether a key inside it matches. A Json field cannot be compared in .where() at all, so use Jsonb unless you specifically need PostgreSQL's plain json column. When you need a path query into the document, reach for raw SQL. Extensions add more types, such as vectors or geometry.

A trailing ? makes a field optional: name String?. A trailing [] makes it a list: tags String[]. A @default(...) must be written in a form the field's type accepts, such as the json literal @default(json`{}`) on a Jsonb field and @default(1.50) on a Decimal field; Default values lists them.

A field can also hold one of a fixed set of values. Declare them in an enum block:

enum Priority {

  @@type("pg/text@1")

  Low  = "low"

  High = "high"

}

@@type says how the values are stored, here as PostgreSQL text, written as the database type plus a version that is always @1 today. On MongoDB, write @@type("mongo/string@1"). You can also leave @@type out, in which case Prisma ORM infers the storage from the members: text for string values and an integer for whole numbers. Enums has the rest of the syntax.

Match the type to what the value means, not to what it looks like.

An identifier is a String, even when it looks numeric. You never add zip codes or phone numbers together, they can have leading zeros, and they do not sort numerically:

model Address {

  id  Int    @id @default(autoincrement())

  zip String

}

Money is not a Float, because floating-point numbers cannot represent decimal amounts exactly and sums drift by fractions of a cent. For ordinary money, use Int and store the amount in the currency's smallest unit, such as cents, which also gives you a plain JavaScript number to work with. Reserve Decimal for values that need more decimal places than the currency has, such as a per-unit rate:

model Product {

  id         Int @id @default(autoincrement())

  priceCents Int

}

A point in time is a DateTime, not a String, because a real timestamp type gives you correct comparison, sorting, and range queries:

model Post {

  id          Int      @id @default(autoincrement())

  publishedAt DateTime

}

When you are unsure between two sizes, lean toward the wider one. For a counter that could grow past two billion, that means BigInt over Int, since changing Int to BigInt later needs a migration that picking BigInt now lets you skip. BigInt does cost you something in your code, though: the value reaches you as a JavaScript bigint, which JSON.stringify refuses to serialize until you call .toString() on it.

Mark a field optional (?) only when "absent" means something different from a sensible default, such as publishedAt DateTime? where no value means unpublished. A required field with a default is often the clearer model.

A relation connects two models: a user has many posts, a post belongs to one user.

A relation uses two kinds of field. The model that holds the link needs both of them, while the model on the other end needs only a relation field:

  • The link field stores the other record's primary key, like authorId. Prisma ORM 7 called this the relation scalar field. On a relational database it is the foreign key column, and on MongoDB it is the field holding the other document's _id.
  • The relation field is typed as the other model, like author User. It creates no column of its own and only tells Prisma ORM how to follow the connection in a query.

The @relation attribute ties them together: fields names the link field, and references names the field it points at on the other model. On the other model, posts Post[] is the other end of the same relation.

model User {

  id    Int    @id @default(autoincrement())

  posts Post[]

}

model Post {

  id       Int  @id @default(autoincrement())

  authorId Int

  author   User @relation(fields: [authorId], references: [id])

}

Relations come in three kinds:

  • One-to-one: a user has at most one profile. The model that holds the link gets @unique on its link field, and the field on User is profile Profile?, not a list. One-to-one has both models written out.
  • One-to-many: a user has many posts, as in the example above.
  • Many-to-many: a post has many tags, and a tag appears on many posts. This needs a model for the join table, with one record per connected pair. Prisma ORM 7 let you write a list field on both sides with no such model, but that form is gone and npx prisma contract emit rejects it, so write the model out as many-to-many shows.

How each kind is stored, and which model should hold the link field, is where relational and document databases differ. Continue with the guide for your database:

Projects created with npm create prisma@latest include the Prisma ORM skills for your coding agent, instruction files that tell it how Prisma ORM 8 works. In an existing project, run npx prisma skills sync to add them, then try prompts that map to each section:

  • "Using the prisma-8 skill, add a Product model with a surrogate id and a unique sku field."
  • "Add a Country reference table keyed by its ISO code."
  • "Review my contract for fields that should be an enum or a DateTime instead of a String."
  • "Connect Post to User with a foreign key and a relation field."
  • Model relational data: one-to-one, one-to-many, many-to-many, and polymorphic relations on PostgreSQL.
  • Model MongoDB data: embed or reference, and polymorphic collections.
  • Generate a migration to get the models you wrote into the database. The loop is: edit the contract, run npx prisma contract emit to check it and regenerate your types (it replaces prisma generate), run npx prisma migration plan to write the migration files, then run npx prisma db migrate to apply them. That page has the flags and explains how the next plan finds its starting point.
  • Query your models once your contract is in place.
Suggest an edit

Propose a replacement for this page. The site team reviews it before applying any changes.

Export
Documentation menu