Prisma Next is in early access.Read the docs

Overview

Describe the data your application needs with models, primary keys, scalar fields, and relations.

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 Next, you define this structure in a contract.prisma file. This file becomes the shared contract between your application code, your database migrations, and your developer tools.

If you are coming from Prisma 7, contract.prisma plays a similar role to the schema.prisma file you used before.

This page introduces the four building blocks of every Prisma Next contract:

These building blocks apply to every database. Where databases differ is in how you model relations, and the relational and MongoDB guides cover that in depth.

Models

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?
}

Every record of a model has its own identity and its own lifecycle. Two posts with the same title are still two different posts, and a user who changes their email address is still the same user.

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

Primary keys

A primary key is the field that uniquely identifies each record. Every model needs one. Mark it with @id:

model User {
  id    Int    @id @default(autoincrement())
  email String
}

On MongoDB, the primary key maps to the document's mandatory _id field, and ObjectId is the idiomatic type for it.

Natural keys

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: a standardized code you receive rather than invent.

Reference tables are the classic fit. A country is its ISO code, and the code never changes:

model Country {
  code String @id
  name String
}

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

Surrogate keys

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.

The reason is stability. Values you might be tempted to use as a key, like an email address or a product SKU, change in practice. 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. You get a stable key and the uniqueness guarantee:

model User {
  id    Int    @id @default(autoincrement())
  email String @unique
}

model Product {
  id  Int    @id @default(autoincrement())
  sku String @unique
}

Which surrogate type to pick

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

// Auto-incrementing integer: smallest and fastest to index,
// ordered by insertion. 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 is wider than an integer and random UUIDs index a little worse, but independent services never collide and nothing is leaked.

// ObjectId: the idiomatic MongoDB key. Generated without
// coordination and embeds its own creation time.
model Post {
  id ObjectId @id @map("_id")
  @@map("posts")
}

Composite keys

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 is the key. Avoid composite keys on ordinary models: everything that points at the model then has to carry all of the key's fields.

Scalar fields

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

TypeStores
StringText
Int32-bit integer
BigInt64-bit integer
FloatFloating-point number
Booleantrue / false
DateTimeTimestamp
JsonArbitrary JSON value
ObjectIdMongoDB document identifier

Prisma Next's type system is extensible, so extensions can add more types, such as vectors or geometry.

Two modifiers change a field's shape:

  • A trailing ? makes the field optional: name String?
  • A trailing [] makes it a list: tags String[]

How to pick a data type

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. Floating-point numbers cannot represent decimal amounts exactly, so sums drift by fractions of a cent. Store an integer number of the smallest unit:

model Product {
  id         Int @id @default(autoincrement())
  priceCents Int
}

A point in time is a DateTime, not a String. 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 (BigInt over Int for a counter that could grow). Widening a type later is a migration; the wider type today is free.

Mark a field optional (?) only when "absent" means something different from a sensible default. A required field with a default is often the clearer model.

Relations

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

Two kinds of field describe a relation:

  • A field that stores the connection. This is a real column or document field, like authorId, holding the other record's primary key.
  • A relation field, typed as the other model, like author User. It stores nothing itself; it tells Prisma Next how to navigate the connection in queries.

The @relation attribute ties them together: fields names the local field that stores the link, and references names the field it points at on the other model.

model Post {
  id       Int  @id @default(autoincrement())
  authorId Int
  author   User @relation(fields: [authorId], references: [id])
}

Relations come in three shapes:

  • One-to-one: a user has at most one profile.
  • One-to-many: a user has many posts.
  • Many-to-many: a post has many tags, and a tag appears on many posts.

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

Prompt your coding agent

Projects scaffolded with create-prisma@next install Prisma Next skills for your coding agent; the prisma-next-contract skill covers contract authoring. Prompts that map to each section:

  • "Using the prisma-next-contract 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."

Next steps

On this page