Core concepts
The ideas every Prisma ORM command and API builds on: contracts, emitting, plans, the database signature, codecs, and the migration graph.
Prisma ORM 8 is the current major version. To meet the evolving needs of developers, it has been rebuilt in TypeScript according to one idea: your application and database follow an explicit, checkable agreement of how all data is structured.
This idea relies upon a small vocabulary which repeats throughout: in the CLI, in the query APIs, and in error messages. The sections below define each term in plain language; each also links to relevant in-depth information.
The contract and the schema
The contract is your description of the data your application needs: the models, their fields, how they relate, and how they map to database tables or collections. You author it in PSL (the Prisma schema language) in a .prisma file, or in TypeScript:
model User {
id Int @id @default(autoincrement())
email String @unique
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
published Boolean @default(false)
userId Int
user User @relation(fields: [userId], references: [id])
}The schema differs in that it is the actual structure of the database: the tables or collections, plus their indexes, that exist right now. The contract lives in your repository; the schema lives in the database. Everything Prisma ORM does is a relationship between the two: queries are typed against the contract, migrations move the schema toward the contract, and verification checks that the schema still satisfies it.
Other tools use "schema" to refer to the file that you write. However, in Prisma ORM, you author a contract and the schema is held by the database; therefore, be aware that when a command or error message mentions the "schema", this refers to the database side.
Read more in The data contract, and author it in PSL or in TypeScript.
Emitting: from source to artifacts
Emitting is the build step that compiles your contract source into two plain files:
bunx prisma@latest contract emitcontract.json: a canonical JSON description of your models, storage layout, and required capabilities.contract.d.ts: the TypeScript types derived from it, which is what makes your queries type-safe.
Every other part of the toolchain reads these artifacts, not your source file. The query APIs read contract.d.ts for types. The migration planner diffs two contract.json files. The runtime verifies contract.json against the database. That is why contract emit comes first in almost every workflow: after any contract change, emit before you plan, migrate, or run.
Emission is deterministic. The same source always produces byte-identical artifacts, so both files are committed to version control and diff cleanly in code review. Think of the pair like package.json and package-lock.json: the source is what you ask for, the artifacts are the exact resolved result.
See contract.json and contract.d.ts for what is inside each file.
Hashes and the database signature
A hash is a short fingerprint computed from a file's content: the same content always produces the same hash, while any change will produce a different one. Because emission is deterministic, hashing contract.json gives an identifier for that exact contract state, the way a Git commit hash identifies an exact state of your code. Contract hashes appear throughout the CLI; a migration, for example, records the hash it starts from and the hash it produces.
The database carries the other half of the agreement: a signature, a small marker record stored in the database itself that names the contract hash the database currently satisfies. db sign writes it, and db migrate updates it each time it applies a migration.
The two halves make the agreement checkable from either side:
- Before executing queries, the runtime can compare the contract that your application was built with against the database's signature and stop when it encounters a mismatch, for example a deploy against an unmigrated database, before it produces incorrect results.
- Before applying a migration, the runner checks that the database's signature matches the contract hash the migration starts from.
When the contract and the database disagree, the resulting state is called drift. db verify is the read-only command that reports it.
Queries compile to plans
A plan is the compiled form of a query: a plain data object holding the statement to run, its parameters, and metadata about what the query touches. Every query, whichever API produced it, becomes a plan before it executes; running the plan is a separate step.
With the SQL query builder, the two steps are visible in your code:
import { db } from "./prisma/db";
const plan = db.sql.public.post
.select("id", "title", "userId")
.where((f, fns) => fns.eq(f.published, true))
.limit(10)
.build();
const publishedPosts = await db.runtime().query(plan);Plans matter for two reasons:
- Every query goes through the same pipeline. However a query is written (the ORM client, a query builder, a raw fragment, or an API that an extension added), it reaches the database as a plan. Middleware sees every query in the same shape, execution works the same way for all of them, and the query APIs can be mixed freely. In addition, one policy (an authorization check, for instance) can sit in one place and see everything.
- A plan is data. The statement and its parameters exist as an object before anything touches the database: middleware can check them, telemetry can record them, and a failed query can report exactly what it ran.
The query APIs
All query APIs are typed against the contract and all produce plans. They differ in how much of the statement you write yourself.
The ORM client is where you start on both databases: model-based queries such as db.orm.public.User.where(...). It is more than a query builder; for example, the .include() operation coordinates several queries on your behalf to serve higher-order needs, relation traversal above all, and hands back one typed result. For more information, start with Reading data.
Beneath it, each database family has a typed builder for the queries that the ORM client cannot express, and a raw escape hatch below that. A builder plan compiles to exactly one statement, so what you build is what runs:
| PostgreSQL | MongoDB | |
|---|---|---|
| Typed builder | The SQL query builder: composable joins, grouping, projections | The pipeline builder: typed aggregation pipelines |
| Raw escape hatch | Raw SQL: fns.raw fragments spliced into builder queries, or whole statements written with db.raw.sql | Raw commands sent to the driver |
Raw queries are still plans, so middleware and telemetry see them as any other query. A whole db.raw.sql statement declares its row shape with .returnsRow(spec), so its rows come back decoded. fns.raw fragments and MongoDB raw commands carry no result shape, so you must handle those values yourself; the raw queries reference explains what that means for different databases.
The stack behind one package
One facade package connects your code to your database. A PostgreSQL project installs @prisma/orm-postgres, and its config helper wires up everything underneath: the database family (SQL), the target dialect (PostgreSQL), the adapter that translates plans into that dialect, and the driver that holds the network connection. These four names recur in error messages and extension docs; day to day, you configure the one facade package and move on.
Prisma ORM's layering exists for extensibility. Our core remains small, because it is focused; everything around it, including PostgreSQL support, plugs in through the same public interfaces. A new database is supported by writing a new target, adapter and driver, without any need to change the core.
Capabilities
A capability is a specific feature that a database may or may not support, such as RETURNING clauses or vector indexes. Your contract declares the capabilities it needs; the adapter reports what the connected database provides. Prisma ORM compares the two at startup, so a missing feature surfaces as one clear error when the app boots, instead of as a failed query later. See Supported database features for more information.
Codecs
A codec converts values between JavaScript and the database's wire format, in both directions. Every column type in your contract is backed by one: a PostgreSQL timestamptz column has a codec that produces a JavaScript Date when you read and encodes it back when you write. This means that when you pick a column type in PSL, you are also picking the codec that will handle every value that column carries.
Extensions bring codecs for the types they add: with pgvector installed, a Vector(1536) column comes back as a typed vector rather than a string. Raw fragments and raw commands skip this conversion step, so with those you convert the values yourself.
Extensions
An extension is an installable package that plugs new pieces into the toolchain: column types and their codecs, query operations, index kinds, capabilities, and, at the widest, support for an entire database. One package extends the contract language, the emitted types, the query builders, and migrations together.
Extensions are declared in prisma.config.ts and registered on the client:
import { definePrismaConfig } from 'prisma/config';
import pgvector from '@prisma/orm-extension-pgvector/control';
import { defineConfig as ormConfig } from '@prisma/orm-postgres/config';
export default definePrismaConfig({
orm: ormConfig({
contract: './src/prisma/contract.prisma',
extensions: [pgvector],
db: {
connection: process.env['DATABASE_URL']!,
},
}),
});Subsequently, pgvector.Vector(1536) is a column type in your contract, vector operators appear in the query builder, and migration plan knows how to create vector indexes. See Using extensions.
Middleware
A middleware is a plain object with a name and one or more hooks that run around every query, following the same principles as in Express or Koa. You need only register it once, in the middleware option of your client setup. Because every query is a plan, middleware gets a structured object to inspect: it can log it, enforce limits on it, or reject it, without changing how queries are written. That makes middleware the place for one policy that must cover the whole app, such as an authorization rule that examines every plan before it runs.
Three middleware ship with Prisma ORM at present, and they are in the early stages: treat them as working demonstrations of the pattern rather than finished products. Budgets caps row counts and surfaces slow queries, lints blocks risky query shapes, and cache serves repeated reads from memory. For policy that your app depends on, write your own; the middleware API is the durable surface. Start by exploring How middleware works.
Migrations: a graph of contracts
A migration is a recorded change that moves the database's schema from the state one contract describes to the state another describes. It is a directory in your repository containing the change as editable TypeScript (migration.ts), the compiled operations that Prisma runs (ops.json), and metadata recording which contract hash it starts from and moves to.
One command applies them: db migrate advances a live database along the recorded migrations. The other migration ... commands never touch a database; they create and inspect the migration files in your repository.
Because every migration records its from and to hashes, the migrations in a repository form a graph: contracts are the nodes, migrations are the edges. When two branches each add a migration and both merge, the graph has a fork and a join, and db migrate finds a path from wherever a database currently is to wherever you want it to be. No renumbering, no rebasing migration files.
A ref is a named pointer at a contract, such as production or staging, managed with migration ref. Refs let deployment commands target an environment by name: db migrate --to production.
If you know Git, the whole vocabulary maps across:
| Git | Prisma ORM |
|---|---|
| A commit | A contract, identified by its hash |
| A patch between commits | A migration |
| A branch or tag | A ref |
HEAD | The database signature |
git checkout <commit> | db migrate --to <contract> |
Start with How migrations work, then The migration graph for the branching story.
How the CLI commands combine
One rule divides the whole CLI: db ... commands connect to a live database and can change it, while contract ... and migration ... commands work on the files in your repository. The one exception is contract infer, which reads a live database, changing nothing in it, to write a starter contract file. If you are unsure what a command might touch, its first word provides the answer: only db can change a database.
The commands compose into four everyday workflows.
The development loop. Edit your contract, emit it, turn the change into a reviewable migration, apply it:
bunx prisma@latest contract emit
bunx prisma@latest migration plan --name add_user_phone
bunx prisma@latest db migratePrototyping without migration files. While a schema is still in flux, skip the migration directory and reconcile the database directly. db update diffs the live schema against the emitted contract and applies the difference; --dry-run previews it first:
bunx prisma@latest contract emit
bunx prisma@latest db update --db "$DATABASE_URL" --dry-run
bunx prisma@latest db update --db "$DATABASE_URL"When the shape settles, switch to migration plan so changes become reviewable files.
Adopting an existing database. contract infer writes a starter contract from a live schema. Review and edit it, emit, then bring the database under contract management: db init applies only additive changes and writes the first signature. If the database already matches the contract exactly, db sign records the signature without changing anything:
bunx prisma@latest contract infer --db "$DATABASE_URL"
bunx prisma@latest contract emit
bunx prisma@latest db init --db "$DATABASE_URL"Checking in CI, deploying in CD. migration check verifies the migration files and graph offline, so it runs in CI with no database. db verify is its live counterpart: a read-only check that a database satisfies the contract. A deploy pipeline pins environments with refs and migrates to them by name:
bunx prisma@latest migration check
bunx prisma@latest db verify --db "$DATABASE_URL"
bunx prisma@latest db migrate --db "$DATABASE_URL" --to productionPrompt your coding agent
Projects scaffolded with create-prisma@latest install Prisma ORM skills for your coding agent. Ask your agent to:
- "Using the prisma-8 skill, explain the difference between our contract and the database schema."
- "Show me the plan the SQL query builder produces for this query."
- "Which of our CLI scripts touch the live database, and which are offline?"
Next steps
- The data contract: the concept that this whole page hangs off, in depth.
- Reading data: put the ORM client to work against your contract.
- How migrations work: change your contract, then plan, review, and apply the migration, hands-on.
