diff --git a/.claude/skills/docs-reader-review/scripts/check-plain.sh b/.claude/skills/docs-reader-review/scripts/check-plain.sh
index d4d2a448d4a..8c8cdef3310 100755
--- a/.claude/skills/docs-reader-review/scripts/check-plain.sh
+++ b/.claude/skills/docs-reader-review/scripts/check-plain.sh
@@ -6,7 +6,7 @@
# "(unexplained)" there (emit, signature, marker, ref, plan) are allowed when the sentence
# defines them, which a regex cannot judge; the reader review covers those.
# What is skipped: fenced code (``` or ~~~, indented or not), indented code blocks,
-# inline code, and any line carrying the marker {/* plain-language:defined */}, which
+# inline code, link targets, pinned heading anchors, href attributes, the front-matter url line, and any line carrying the marker {/* plain-language:defined */}, which
# an author adds only when the sentence itself defines the term in plain words.
set -u
pat='\bterminal (calls?|methods?|verbs?)\b|\b(read|write|query) terminals?\b|\bcodecs?\b|\benvelopes?\b|\btype position\b|\brelease (line|cycle)\b|\bruns ahead\b|\b(sql|raw|orm) lanes?\b|\blane object\b|\bfacades?\b|\bjunction (model|record|table)\b|\bjoin model\b|\bquery plans?\b|\blower(ed|ing)\b|\bnamespace-qualified\b|\bnamespace coordinate\b|\breducers?\b|\brefinement callback\b|\bdotted codes?\b|\b(CLI|config) envelope\b|\bdev loop\b|\bbrownfield\b|\bgreenfield\b|\bdeterministic\b|\bidempotent\b|\btopology\b|\bcapabilit(y|ies)\b|\bbuffer(s|ed)? (the|every|all)\b|\bmaterializ(e|es|ed)\b|\bunblocks?\b|\bAPI surface\b|\bsurfaces? (the|a|an|as)\b|\bwires? up\b|\blives at\b|\bhands back\b|\brides on\b|\broutes on\b|\bkeys on\b'
@@ -17,8 +17,9 @@ for f in "$@"; do
infence { next }
/^( |\t)/ { next }
/plain-language:defined/ { next }
+ /^url: / { next }
{ print NR": "$0 }
- ' "$f" | sed 's/`[^`]*`//g' | grep -E -i "$pat" || true)
+ ' "$f" | sed -E 's/`[^`]*`//g; s/\]\([^)]*\)/]/g; s/\[#[^]]*\]//g; s/href="[^"]*"//g' | grep -E -i "$pat" || true)
if [ -n "$hits" ]; then status=1; echo "== $f"; echo "$hits" | cut -c1-160; fi
done
if [ $status -eq 0 ]; then echo "plain-language check: clean"; else echo "plain-language check: banned terms found"; fi
diff --git a/apps/docs/content/docs/(index)/prisma-orm/meta.json b/apps/docs/content/docs/(index)/prisma-orm/meta.json
index 8c1ad499fd8..aadc57bc05b 100644
--- a/apps/docs/content/docs/(index)/prisma-orm/meta.json
+++ b/apps/docs/content/docs/(index)/prisma-orm/meta.json
@@ -1,5 +1,12 @@
{
"title": "Prisma ORM",
"defaultOpen": true,
- "pages": ["index", "release-status", "create-prisma", "quickstart", "add-to-existing-project"]
+ "pages": [
+ "index",
+ "release-status",
+ "supported-databases",
+ "create-prisma",
+ "quickstart",
+ "add-to-existing-project"
+ ]
}
diff --git a/apps/docs/content/docs/(index)/prisma-orm/release-status.mdx b/apps/docs/content/docs/(index)/prisma-orm/release-status.mdx
index 6ba9592f9a6..612f4b5377b 100644
--- a/apps/docs/content/docs/(index)/prisma-orm/release-status.mdx
+++ b/apps/docs/content/docs/(index)/prisma-orm/release-status.mdx
@@ -33,7 +33,7 @@ npm install --save-dev prisma
npm install @prisma/orm-postgres
```
-For MongoDB the library is `@prisma/orm-mongo`, and for SQLite it is `@prisma/orm-sqlite`. MySQL is not supported yet.
+For MongoDB the library is `@prisma/orm-mongo`. [Supported databases](/prisma-orm/supported-databases) lists every database and its status.
The two packages have different version numbers, and that is normal: they are released separately, and the command-line tool gets more frequent releases. Install both at `latest`; any two `latest` versions work together, and you do not pair the numbers yourself.
diff --git a/apps/docs/content/docs/(index)/prisma-orm/supported-databases.mdx b/apps/docs/content/docs/(index)/prisma-orm/supported-databases.mdx
new file mode 100644
index 00000000000..0b304593f9e
--- /dev/null
+++ b/apps/docs/content/docs/(index)/prisma-orm/supported-databases.mdx
@@ -0,0 +1,36 @@
+---
+title: Supported databases
+description: 'Which databases Prisma ORM 8 supports today, what release candidate and early access mean, and which databases are coming.'
+url: /prisma-orm/supported-databases
+metaTitle: Databases supported by Prisma ORM 8
+metaDescription: 'PostgreSQL is a release candidate and MongoDB is in early access. MySQL, MariaDB, SQLite, SQL Server, and CockroachDB are coming. What each label means and how to ask for a database.'
+---
+
+Prisma ORM 8 ships one library per database. Today there are two you can build on.
+
+| Database | Status | Library |
+| --- | --- | --- |
+| PostgreSQL, including Prisma Postgres | Release candidate | `@prisma/orm-postgres` |
+| MongoDB | Early access | `@prisma/orm-mongo` |
+| MySQL | Coming soon | |
+| MariaDB | Coming soon | |
+| SQLite | Coming soon | |
+| Microsoft SQL Server | Coming soon | |
+| CockroachDB | Coming soon | |
+
+## What the labels mean
+
+**Release candidate.** Install it and build with it today. Until the final release, some details of the API may still change, so code you write now may need small edits later. Each release candidate fixes bugs found in the last one. [Release status](/prisma-orm/release-status) has the dates and what `npm install` gives you.
+
+**Early access.** The library works and you can build with it, but some of what the PostgreSQL library does is not there yet. The reference pages say on each method whether it works on MongoDB, and [what is not available yet](/orm/coming-from-prisma-orm-7#not-available-yet) lists the gaps. Report what you hit on [GitHub](https://github.com/prisma/orm/issues).
+
+**Coming soon.** There is no Prisma ORM 8 library for the database yet. Prisma ORM 7 supports it, and Prisma ORM 7 keeps receiving bug fixes and security updates for 18 months after Prisma ORM 8 reaches general availability. [How to stay on Prisma ORM 7](/prisma-orm/release-status#how-to-stay-on-prisma-orm-7) shows how to lock the version.
+
+## How to ask for a database
+
+Add a 👍 to the GitHub issue for the database you need, or open one if there is none. The number of reactions is one of the inputs to what gets built next. Organizations that need a database sooner can talk to [Prisma enterprise support](https://www.prisma.io/enterprise).
+
+## Next steps
+
+- The quickstart sets up a project on [PostgreSQL](/prisma-orm/quickstart/postgresql) or [MongoDB](/prisma-orm/quickstart/mongodb).
+- [Release status](/prisma-orm/release-status) covers versions, dates, and staying on Prisma ORM 7.
diff --git a/apps/docs/content/docs/guides/v7/meta.json b/apps/docs/content/docs/guides/v7/meta.json
index be47531cbec..d7bb19db405 100644
--- a/apps/docs/content/docs/guides/v7/meta.json
+++ b/apps/docs/content/docs/guides/v7/meta.json
@@ -7,6 +7,9 @@
"deployment",
"database",
"switch-to-prisma-orm",
- "integrations"
+ "integrations",
+ "---Upgrade Prisma ORM---",
+ "[Prisma ORM 7 to 8 (PostgreSQL)](/guides/upgrade-prisma-orm/postgresql)",
+ "[Prisma ORM 6 to 8 (MongoDB)](/guides/upgrade-prisma-orm/mongodb)"
]
}
diff --git a/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx b/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx
index 61e8d74dca3..54f924cbaaf 100644
--- a/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx
+++ b/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx
@@ -1,97 +1,97 @@
---
-title: Capabilities
-description: 'Capabilities record what your database stack supports, so Prisma ORM can reject unsupported features early with a clear error.'
+title: Supported database features
+description: 'The contract records which database features your packages support, so Prisma ORM can reject an unsupported one early with a clear error.'
+metaTitle: Supported database features in Prisma ORM
+metaDescription: 'How a Prisma ORM contract records which database features you can use, where the list comes from, what each key controls, and what happens when one is missing.'
url: /orm/contract-authoring/capabilities
-metaTitle: Capabilities in Prisma ORM
-metaDescription: 'How Prisma ORM capabilities are derived from the target, adapter, and extension packs, what they gate, and what happens when a required capability is missing.'
---
-Not every database stack supports every feature. One setup can run lateral joins, `RETURNING` clauses, and vector distance operations; another cannot.
+Your [contract](/orm/contract-authoring/the-data-contract), the `contract.prisma` file that replaced `schema.prisma`, describes your data. [`npx prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` beside it. The `capabilities` section of `contract.json` lists the database features you can use, under keys such as `sql.lateral`. Prisma ORM writes that section, and you never edit it. If you use a feature that is not listed, Prisma ORM raises an error instead of sending the query to the database.
-Capabilities are how the [data contract](/orm/contract-authoring/the-data-contract) records what yours supports. Prisma ORM checks them before using a gated feature. An unsupported feature fails early with an error that names the missing capability, instead of surfacing as a database error mid-query.
+## Where the `capabilities` section comes from [#where-capabilities-come-from]
-## Where capabilities come from
+The keys come from your database package (`@prisma/orm-postgres`, `@prisma/orm-sqlite`, or `@prisma/orm-mongo`) and any extension packages listed in `prisma.config.ts`. `npx prisma contract emit` never connects to your database. It reads those packages and writes what they report, in groups:
-You do not write capabilities yourself. When [`contract emit`](/cli/contract-emit) runs, it merges the capability declarations of every component composed in your project: the target (PostgreSQL, SQLite, MongoDB), its adapter and driver, and any extension packs from the config. The merged result lands in the contract's `capabilities` section, grouped by namespace:
-
-```json title="prisma/contract.json (excerpt)"
+```json title="src/prisma/contract.json (excerpt)"
{
"capabilities": {
- "postgres": {
- "distinctOn": true,
- "jsonAgg": true,
- "lateral": true,
- "limit": true,
- "orderBy": true,
- "pgvector.cosine": true,
- "returning": true
- },
- "sql": {
- "defaultInInsert": true,
- "enums": true,
- "lateral": true,
- "returning": true,
- "scalarList": true
- }
+ "postgres": { "distinctOn": true, "pgvector.cosine": true },
+ "sql": { "lateral": true, "scalarList": true }
}
}
```
-The `sql` namespace holds keys shared across SQL databases; the `postgres` namespace holds PostgreSQL-specific keys. Extension packs contribute keys of their own: composing the pgvector pack is what adds `pgvector.cosine` above. Because the packs declare the keys, adding or removing an extension in `prisma.config.ts` and re-running `contract emit` changes the capability set with no further work.
+Error messages and the table below name a key group first, `postgres.pgvector.cosine`. The `sql` group holds keys that more than one SQL database may support. SQLite reports `false` for some of them. Keys under `postgres` belong to PostgreSQL only. Extension packages add keys of their own, and `pgvector.cosine` comes from the pgvector package. Install it with `npm install @prisma/orm-extension-pgvector`, list it in `prisma.config.ts`, and run `npx prisma contract emit` again:
+
+```typescript title="prisma.config.ts"
+import "dotenv/config";
+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']! },
+ }),
+});
+```
-## What capabilities gate
+pgvector is also a PostgreSQL server extension, and the package ships a migration that installs it. Apply it with `npx prisma db migrate`, see [Using extensions](/orm/extensions/using-extensions). Run `npx prisma contract emit` after every change to the contract or to the extension list.
-Capabilities are checked at two points, both before any SQL reaches the database.
+## What the `capabilities` section controls [#what-capabilities-gate]
-**When the contract is built.** If the schema uses a feature the target cannot store, `contract emit` fails with a diagnostic. For example, SQLite does not report the `scalarList` capability, so a scalar list field in a contract targeting SQLite fails to emit:
+**When the contract is built.** If your contract uses a feature your database package does not support, `npx prisma contract emit` fails. SQLite has no `scalarList` key, so a list field such as `tags String[]` fails on SQLite:
```text
Field "User.tags" is a scalar list, but target "sqlite" does not support
scalar lists (the adapter does not report the "scalarList" capability).
+Remove the list or author it against a target that supports scalar lists.
```
-**When a query is built.** Query-builder methods that depend on a capability check the contract's `capabilities` section and throw if the key is missing, naming the method and the capability:
+In that message, both words mean your database package, and the message's `"scalarList"` is the `sql.scalarList` key. Do one of the two: remove the list field, or move to a database that supports list fields. To move to PostgreSQL, change the `@prisma/orm-sqlite/config` import and the connection string in `prisma.config.ts`, and create the new database.
+
+**When you call a method on the [SQL query builder](/orm/reference/sql-query-builder) or on the ORM client.** A method that needs a feature throws an error whose `code` is `ORM.CAPABILITY_MISSING`. The message names the method and the key:
```text
distinctOn() requires capability postgres.distinctOn
-lateralJoin() requires capability sql.lateral
```
-The error fires when your code constructs the query, so a test that builds the query catches it: a query that uses a feature your stack lacks cannot be built at all.
+That message means the method is not available on the database package you chose. Rewrite the query without it, or move to a database that supports the feature. If the key comes from an extension package, install that package and list it in `prisma.config.ts`. You do not have to run the query to get the error, so a test that only builds the query still fails. Your own code can read the same list through `db`, the client you create once in `src/prisma/db.ts`, which `npx prisma orm init` writes. A value such as `db.contract.capabilities.sql?.lateral` is `true` or `false`, or `undefined` if the whole group is missing. For a key with a dot in it, write `db.contract.capabilities.postgres?.["pgvector.cosine"]`:
+
+```typescript
+if (db.contract.capabilities.sql?.lateral) {
+ // run the lateral join here, or fall back to a second query
+}
+```
-## Example capabilities
+## Example keys [#example-capabilities]
-A few of the keys the built-in components declare, to give a sense of the granularity:
+A few of the keys your database package and extension packages supply:
-| Capability | Gates |
+| Key | What it covers |
| --- | --- |
-| `sql.lateral` | Lateral joins (`lateralJoin()`); PostgreSQL declares it, SQLite does not |
+| `sql.lateral` | Lateral joins (`lateralJoin()`) |
| `sql.returning` | `RETURNING` clauses on writes |
-| `sql.enums` | Enum value sets enforced in the database; SQLite does not declare it |
-| `sql.defaultInInsert` | Using `DEFAULT` as a value in multi-row inserts |
-| `sql.scalarList` | Scalar list fields in the schema |
+| `sql.scalarList` | List fields such as `tags String[]` |
| `postgres.distinctOn` | `DISTINCT ON` queries (`distinctOn()`) |
-| `postgres.jsonAgg` | JSON aggregation for nested reads |
-| `postgres.pgvector.cosine` | Cosine distance operations, contributed by the pgvector pack |
-
-This table is a sample, not the full registry. Each target, adapter, and pack ships its own declarations. Your project's emitted `contract.json` is the authoritative list of what your stack supports.
-
-MongoDB currently declares no capability keys: the capability system mostly differentiates SQL targets and their extensions, and the MongoDB pipeline does not yet gate features this way.
+| `postgres.pgvector.cosine` | Cosine distance from the pgvector package |
-## Capabilities and verification
+PostgreSQL reports `true` for all three `sql` keys above. SQLite reports `false` for `sql.lateral`, `true` for `sql.returning`, and has no `sql.scalarList` key at all. This table is a sample, not the full list. Open `src/prisma/contract.json` in your project to see every key you have. The MongoDB packages report no keys, so neither check applies on MongoDB. The [ORM reference](/orm/reference) lists the methods each database has.
-Capabilities describe what the composed software stack supports, as declared by the target, adapter, and packs. They are recorded in the contract at emit time rather than probed from the live database, so the same contract behaves identically in every environment. Database-side verification is separate: [`db verify`](/cli/db-verify) checks that the database matches the contract's schema and profile, using the hashes described in [the contract artifact](/orm/contract-authoring/the-contract-artifact#the-content-hashes).
+## The `capabilities` section and `prisma db verify` [#capabilities-and-verification]
-Extensions are the main source of capabilities beyond the core. [Using extensions](/orm/extensions/using-extensions) shows the full flow from install to first query.
+[`npx prisma db verify`](/cli/db-verify) checks the live database against the contract's tables and columns, not against this section. The keys say what your packages support, recorded when you run `npx prisma contract emit`.
## Prompt your coding agent
-Projects scaffolded with `create-prisma@latest` install [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. The `prisma-8` skill covers this page. Ask your agent to:
+Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. In an existing project, run `npx prisma skills sync`. The `prisma-8` skill covers this material. Ask your agent to:
-- "Which capabilities does our contract currently require, and which package provides each?"
-- "Add pgvector to the project and confirm the capability shows up in the emitted contract."
+- "Which keys in the `capabilities` section does our code rely on, and which package provides each?"
+- "Add pgvector to the project and confirm its key shows up in the emitted contract."
## Next steps
-- See where the `capabilities` block sits in [the contract artifact](/orm/contract-authoring/the-contract-artifact).
-- Compose extension packs, and the capabilities they bring, in the [CLI configuration](/cli/configuration).
+- See where the `capabilities` block is, and what `contract.d.ts` holds, in [contract.json and contract.d.ts](/orm/contract-authoring/the-contract-artifact).
+- Add extension packages, and the keys they bring, in the [CLI configuration](/cli/configuration).
diff --git a/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx b/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx
index 37fd5fd2019..1a23dc70877 100644
--- a/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx
+++ b/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx
@@ -1,18 +1,26 @@
---
title: Author in PSL
-description: 'Write the Prisma ORM contract as a Prisma schema file, using the schema language you already know plus the Prisma ORM 8 additions.'
+description: 'Write the Prisma ORM contract in the Prisma schema language you already know, plus the Prisma ORM 8 additions.'
url: /orm/contract-authoring/psl-syntax
metaTitle: Author the Prisma ORM contract in PSL
metaDescription: 'Learn how to write a Prisma ORM contract in the Prisma schema language, including named types, enums, value objects, relations, and extension types.'
---
-PSL is the preferred way to author the Prisma ORM [data contract](/orm/contract-authoring/the-data-contract). You write a Prisma schema file, usually `prisma/contract.prisma`, and [`contract emit`](/cli/contract-emit) turns it into `contract.json` and `contract.d.ts`.
+PSL, the Prisma Schema Language, is the preferred way to author [your contract](/orm/contract-authoring/the-data-contract), the `contract.prisma` file that replaced `schema.prisma`. You write one file, usually `src/prisma/contract.prisma`, and [`npx prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` beside it. If you know the Prisma schema language, most of a contract file reads exactly as you expect. Prisma ORM 8 differs in five places:
-If you know the Prisma schema language, most of a contract file reads exactly as you expect. This page covers the shared basics briefly and the Prisma ORM 8 additions in detail: named types, typed enums, value objects, base models with variants, and extension types.
+- named types: give a database column type a name you can reuse on many fields.
+- enums: an enum can now say how its values are stored, and what each member stores.
+- value objects: a structured value stored inside its parent row, with no table of its own.
+- base models and variants: one table can hold more than one kind of record. Put the shared fields in a base model, put the differences in each variant, and Prisma ORM uses one column to tell them apart.
+- extension types: field types that come from an npm package, such as vectors.
+
+The `datasource` and `generator` blocks are gone. The connection URL and the file paths are set in `prisma.config.ts` instead. [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7#schema) lists every change to the schema file, including what each old `@db.` attribute becomes.
## A complete contract
-```prisma title="prisma/contract.prisma" tab="PostgreSQL"
+Every contract starts with `// use prisma-8`. Keep that line at the top.
+
+```prisma title="src/prisma/contract.prisma" tab="PostgreSQL"
// use prisma-8
types {
@@ -56,7 +64,7 @@ model Post {
}
```
-```prisma title="prisma/contract.prisma" tab="MongoDB"
+```prisma title="src/prisma/contract.prisma" tab="MongoDB"
// use prisma-8
type Address {
@@ -75,9 +83,7 @@ enum UserRole {
model User {
id ObjectId @id @map("_id")
- name String
email String
- bio String?
role UserRole
address Address?
posts Post[]
@@ -86,11 +92,9 @@ model User {
}
model Post {
- id ObjectId @id @map("_id")
- title String
- content String
- authorId ObjectId
- createdAt DateTime
+ id ObjectId @id @map("_id")
+ title String
+ authorId ObjectId
author User @relation(fields: [authorId], references: [id])
@@ -99,78 +103,68 @@ model Post {
}
```
-Run `npx prisma@latest contract emit` after any change to refresh the artifacts.
+`Uuid` is PostgreSQL's `uuid` type written as a field type. Run `npx prisma contract emit` after any change to refresh `contract.json` and `contract.d.ts`. Then [`npx prisma db init`](/cli/db-init) creates the tables.
## Point the config at the schema
-The config's `contract` path names the source of truth. A `.prisma` extension selects PSL authoring:
+The config's `contract` path names the one file Prisma ORM reads. It takes a single path, so there is no folder of contract files. If the path ends in `.prisma`, Prisma ORM reads it as PSL. If it ends in `.ts`, as TypeScript. The `db` key holds the connection URL:
-```typescript title="prisma.config.ts" tab="PostgreSQL"
+```typescript title="prisma.config.ts"
+import 'dotenv/config';
import { definePrismaConfig } from "prisma/config";
import { defineConfig as ormConfig } from "@prisma/orm-postgres/config";
export default definePrismaConfig({
orm: ormConfig({
- contract: "./prisma/contract.prisma",
+ contract: "./src/prisma/contract.prisma",
+ db: {
+ connection: process.env['DATABASE_URL']!,
+ },
}),
});
```
-```typescript title="prisma.config.ts" tab="MongoDB"
-import { definePrismaConfig } from "prisma/config";
-import { defineConfig as ormConfig } from "@prisma/orm-mongo/config";
-
-export default definePrismaConfig({
- orm: ormConfig({
- contract: "./prisma/contract.prisma",
- }),
-});
-```
-
-Scaffolded projects (`npm create prisma@latest`) already contain this wiring and a starter schema.
+`npx prisma orm init` writes a config like this, `DATABASE_URL` included. The import chooses the database: `@prisma/orm-postgres/config` makes it a PostgreSQL project, and `@prisma/orm-mongo/config` a MongoDB one. [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7#schema) lists the other keys.
## Models and fields
-Models declare fields with a type, an optional `?` marker, and attributes:
+Models declare fields with a type, an optional `?` marker, and attributes. [Scalar fields](/orm/data-modeling#scalar-fields) lists the types a field can hold, among them `String`, `Int`, `Boolean`, `Decimal`, `DateTime`, `Json`, and `Bytes`. You can write a PostgreSQL type wherever you would write `String` or `DateTime`. The PostgreSQL types you can write are `VarChar`, `Char`, `Numeric`, `Timestamp`, `Timestamptz`, `Time`, `Timetz`, `Date`, `Uuid`, `Inet`, `SmallInt`, and `Real`, plus `DateString`, `TimestampString`, `TimestamptzString`, and `TimeString`, which store the same columns but read back as text. On PostgreSQL, plain `String` is a `text` column, `Int` is `int4`, and `DateTime` is `timestamptz`.
-- `@id` marks the primary key; `@@id([a, b])` declares a composite key.
-- `@unique` adds a unique constraint; `@@index([...])` declares a secondary index.
-- `@default(...)` sets a default. Database function defaults such as `@default(now())` become column defaults in the database. Generated defaults such as `@default(uuid())` are applied by Prisma ORM before each write instead, so they work the same on every database. They appear in the contract's `execution` section rather than as DDL.
-- `@map("column_name")` sets a field's physical name; `@@map("table_name")` sets the table or collection name when it differs from the model name.
+- `@id` marks the primary key. `@@id([a, b])` declares a composite key.
+- `@unique` adds a unique constraint on one field. `@@unique([userId, title])` adds one across several fields.
+- `@@index([...])` declares a secondary index.
+- `@default(...)` sets a default. Database function defaults such as `@default(now())` become column defaults in the database. Generated defaults such as `@default(uuid())` come from Prisma ORM, and the database will not fill them in for you, so a row written by raw SQL or another application gets no value.
+- `@map("column_name")` sets a field's column name in the database. `@@map("table_name")` sets the table or collection name when it differs from the model name.
-How IDs map differs by database:
-
-
-
-
+`@updatedAt` is gone. Write `temporal.updatedAt()` where the field's type would go:
```prisma
-model User {
- id Uuid @id @default(uuid())
+model Post {
+ updatedAt temporal.updatedAt()
}
```
-The primary key is an ordinary column; pick its type and default yourself.
+Prisma ORM sets the field to the current time on every create and update. `temporal` is built in. You do not import it or declare it. On PostgreSQL the column is `timestamptz`, and you can still set the field yourself on a write. `temporal.createdAt()` sets the field once, when the row is created.
-
+How IDs map differs by database:
-
+```prisma tab="PostgreSQL"
+model User {
+ id Uuid @id @default(uuid())
+}
+```
-```prisma
+```prisma tab="MongoDB"
model User {
id ObjectId @id @map("_id")
}
```
-The primary key is MongoDB's `_id`: type it `ObjectId` and map it to the physical `_id` key.
-
-
-
-
+On PostgreSQL the primary key is an ordinary column, so pick its type and default yourself. On MongoDB the primary key is the document's `_id`. Type it `ObjectId` and map it to `_id`.
## Named types
-The `types` block declares reusable type aliases. An alias binds a base type and its storage details under one name:
+The `types` block gives a database column type a name you can reuse on many fields:
```prisma
types {
@@ -178,11 +172,11 @@ types {
}
```
-Fields then use `ShortName` like any built-in type. The alias keeps the storage decision (a `varchar(35)` column rather than `text`) in one place. Native PostgreSQL types are written in type position like this, so a field can also use `VarChar(35)`, `Uuid`, or `Timestamptz` directly without an alias.
+Fields then use `ShortName` like any built-in type. The name keeps the column decision in one place: a `varchar(35)` column rather than `text`. A name is optional, and a field can use the native PostgreSQL type directly, such as `VarChar(35)`, `Uuid`, or `Timestamptz`. In Prisma ORM 8 the native type is the field's type, so `String @db.VarChar(35)` from Prisma ORM 7 becomes `VarChar(35)`. The `@db.` attributes are gone. The `types` block is PostgreSQL only.
## Enums
-An enum in Prisma ORM declares its storage codec with `@@type` and, optionally, the stored value for each member:
+An enum lists its members. It can also say how their values are stored, with `@@type`, and what each member stores:
```prisma
enum Priority {
@@ -193,11 +187,28 @@ enum Priority {
}
```
-`@@type("pg/text@1")` stores the values as `text` on PostgreSQL. When a member has no explicit value, the member name itself is stored. In the database, Prisma ORM enforces the allowed values with a `CHECK` constraint on each column that uses the enum.
+In `pg/text@1`, `pg` is PostgreSQL, `text` is the column type, and `@1` is the version of how the value is stored and read. Write `@@type("pg/int4@1")` to store the values as integers instead. When a member has no explicit value, the member name itself is stored.
+
+`@@type` is optional. Leave it out and Prisma ORM picks the type from the member values: bare member names and string values give the database's text type, and integer values give its integer type. Give every member the same kind of value, because a mix of string and integer values throws an error whose `code` is `PSL_ENUM_CANNOT_INFER_TYPE`. A `@default` names the member, as in `priority Priority @default(Low)`, even where the member stores a different value.
+
+An `enum` block is not a PostgreSQL `enum` type: the column is text or an integer. For a PostgreSQL `enum` type, declare it in a `native_enum` block and type the field `pg.enum(Role)`:
+
+```prisma
+native_enum Role {
+ admin = "admin"
+ member = "member"
+}
+
+model User {
+ role pg.enum(Role)
+}
+```
+
+Each member needs a value. `pg` comes with `@prisma/orm-postgres`, so there is nothing to import. You do not create the PostgreSQL type yourself: `npx prisma migration plan` includes the `CREATE TYPE`.
## Value objects
-A `type` block declares a value object: a structured value that belongs to its parent record and has no identity or table of its own.
+A `type` block declares a value object: a structured value stored inside its parent row, with no table of its own.
```prisma
type Address {
@@ -208,16 +219,17 @@ type Address {
}
model User {
- id Uuid @id @default(uuid())
- address Address?
+ id Uuid @id @default(uuid())
+ address Address?
+ addresses Address[]
}
```
-Storage differs by database: on PostgreSQL a value object field lives in a single `jsonb` column, and on MongoDB it is an embedded document. Either way, `contract.d.ts` types it as a structured object rather than untyped JSON. On MongoDB, whether to embed or reference is the central modeling decision; [MongoDB data modeling](/orm/data-modeling/mongodb#embed-or-reference) covers it.
+A value object field can be optional or a list, and a `type` block can hold a field of another `type`. Watch the two spellings: `types { ... }` declares named types, and `type X { ... }` declares a value object. Storage differs by database. On PostgreSQL a value object field is stored in a single `jsonb` column. On MongoDB it is an embedded document. Either way, `contract.d.ts` types it as a structured object rather than untyped JSON. On MongoDB, whether to embed or reference is the central modeling decision. [MongoDB data modeling](/orm/data-modeling/mongodb#embed-or-reference) covers it.
## Relations
-Relations use the `@relation` syntax you know from Prisma ORM. The side that holds the foreign key declares the scalar field and the mapping; the other side declares a list:
+Relations use the `@relation` syntax you know from Prisma ORM. The side that holds the foreign key declares the scalar field and the mapping. The other side declares a list:
```prisma
model Post {
@@ -230,7 +242,16 @@ model User {
}
```
-Many-to-many relations go through an explicit join model with a composite primary key. The list fields on both sides then resolve through it:
+Add `onDelete` and `onUpdate` to the same `@relation`. They belong on the side that holds the foreign key, not on the list side. For a one-to-one, make the other side singular instead of a list, so `User` declares `profile Profile?`, and put `@unique` on the foreign-key field:
+
+```prisma
+model Profile {
+ userId Uuid @unique
+ user User @relation(fields: [userId], references: [id])
+}
+```
+
+Many-to-many relations need a model for the join table. You write that model yourself: there is no implicit many-to-many. That model must follow two rules. If a side has a composite primary key, that model needs one foreign-key field for each part of it. Its `@@id([...])` must list exactly the foreign-key fields, and nothing else.
```prisma
model Post {
@@ -253,11 +274,13 @@ model PostTag {
}
```
-The emitted contract records the relation as `N:M` with the join table's columns, so queries can traverse `post.tags` directly.
+You then read `post.tags` as a list of `Tag`, without mentioning `PostTag` in the query. Break either rule and `npx prisma contract emit` reports which list field it could not match to a model. If two models qualify, `npx prisma contract emit` throws an error whose `code` is `PSL_AMBIGUOUS_BACKRELATION`. Put the same `@relation("name")` on both ends of one pair, so on `PostTag.post` for `Post.tags`.
+
+For which shape to choose and which side owns the foreign key, see [relational data modeling](/orm/data-modeling/relational-databases) and [MongoDB data modeling](/orm/data-modeling/mongodb).
## Base models and variants
-A base model declares a discriminator field, and each variant names its base and its discriminator value:
+A base model declares a discriminator field, the field whose value says which variant a row is. Each variant names its base and its discriminator value:
```prisma
model Task {
@@ -278,26 +301,30 @@ model Bug {
}
```
-Rows with `type = "bug"` are `Bug` records. On PostgreSQL, the variant's `@@map` picks its storage layout: with its own `@@map`, as here, the variant's fields live in their own table sharing the base model's primary key. Without one, they live in the base table as nullable columns. [Relational data modeling](/orm/data-modeling/relational-databases#polymorphic-relations) covers choosing between the two. On MongoDB, variants add their fields to documents in the base model's collection, so a variant declares `@@base` but no `@@map` of its own.
+A field name is written bare, as `type` is in `@@discriminator(type)`, and a database name is quoted, as in `@@map("task")`. Rows whose `type` column holds `"bug"` are `Bug` records. A variant reuses its base model's fields, so a `Bug` has `id`, `title`, and `type` as well as `severity` and `stepsToRepro`. A variant does not declare an `@id` of its own: it takes the base model's primary key.
+
+You query a variant through the base model: `db.orm.public.Task.variant('Bug')` returns the `Bug` rows only. Here `public` is the PostgreSQL schema. Writing a row of a variant starts with the same `variant('Bug')` call. See [`variant()`](/orm/reference/orm-client#variant).
+
+On a variant, `@@map` does more than rename. On PostgreSQL it chooses between two storage layouts. Give the variant its own `@@map`, as `Bug` has here, and its fields are in a table of their own that shares the base model's primary key. Leave `@@map` out and they are nullable columns in the base table. [Relational data modeling](/orm/data-modeling/relational-databases#polymorphic-relations) covers choosing between the two.
+
+On MongoDB, a variant adds its fields to documents in the base model's collection, so it declares `@@base` but no `@@map` of its own.
## Extension types
-Extension packs contribute types through constructor expressions in the `types` block. Compose the pack in the config, then use its types:
+An extension pack is an npm package that adds field types Prisma ORM does not ship, such as vectors. Install the pack, add the import and the `extensions` line to the config shown above, then call its types in the `types` block:
+
+```bash
+npm install @prisma/orm-extension-pgvector
+```
```typescript title="prisma.config.ts"
-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: "./prisma/contract.prisma",
+// inside ormConfig({ ... }), beside contract and db:
extensions: [pgvector],
- }),
-});
```
-```prisma title="prisma/contract.prisma"
+```prisma title="src/prisma/contract.prisma"
types {
Embedding1536 = pgvector.Vector(1536)
}
@@ -308,17 +335,15 @@ model Post {
}
```
-`contract emit` validates extension types against the packs composed in the config: a type from a pack that is not listed fails the emit with a diagnostic. Re-run `contract emit` after changing the extension list.
+The `pgvector` part of `pgvector.Vector(1536)` is a fixed name the pack declares, not the name you gave the import. List the pack before using its types. Run `npx prisma contract emit` again after changing the extension list. [Using extensions](/orm/extensions/using-extensions) covers installing a pack and names the packs you can add.
## Starting from an existing database
-If the database already exists, don't write the schema by hand. [`contract infer`](/cli/contract-infer) reads the live schema and writes a starter `contract.prisma` for you to review and edit.
-
-The syntax above declares relations. For which shape to choose and which side owns the foreign key, see [relational data modeling](/orm/data-modeling/relational-databases) and [MongoDB data modeling](/orm/data-modeling/mongodb).
+If the database already exists, don't write the contract by hand. [`contract infer`](/cli/contract-infer) reads the live schema and writes a starter `contract.prisma` for you to review and edit.
## Prompt your coding agent
-Projects scaffolded with `create-prisma@latest` install [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. The `prisma-8` skill covers this page. Ask your agent to:
+Projects created with `npm create prisma@latest -- my-app` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. Skills are instruction files the agent reads. In an existing project, run `npx prisma skills sync`. The `prisma-8` skill covers PSL authoring. Ask your agent to:
- "Using the prisma-8 skill, add a Status enum stored as text and use it on the Order model."
- "Add a one-to-many between User and Post with the foreign key on Post."
@@ -326,6 +351,6 @@ Projects scaffolded with `create-prisma@latest` install [Prisma ORM skills](/ai/
## Next steps
-- Emit and inspect [the artifacts](/orm/contract-authoring/the-contract-artifact) the schema produces.
+- Run `npx prisma contract emit` and inspect [`contract.json` and `contract.d.ts`](/orm/contract-authoring/the-contract-artifact). You do not import them yourself. `db`, the client that reads both, is in `src/prisma/db.ts`, and `prisma orm init` writes that file. See [transactions and runtime](/orm/reference/transactions-and-runtime).
- If you prefer defining models in code, see [authoring in TypeScript](/orm/contract-authoring/typescript-schema-builder).
-- Apply the contract to a database with [`db init`](/cli/db-init) or plan changes with [`migration plan`](/cli/migration-plan).
+- Plan changes to a database you have already created with [`migration plan`](/cli/migration-plan).
diff --git a/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx b/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx
index 85b9c675fab..7952cb9a545 100644
--- a/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx
+++ b/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx
@@ -1,55 +1,55 @@
---
-title: The emitted artifacts
-description: contract.json and contract.d.ts are the deterministic artifacts every other part of Prisma ORM consumes. Here is what is inside them.
+title: contract.json and contract.d.ts
+description: 'contract.json and contract.d.ts are the two files every other part of Prisma ORM reads. Here is what is inside them.'
url: /orm/contract-authoring/the-contract-artifact
-metaTitle: The Prisma ORM contract artifacts
-metaDescription: 'What contract.json and contract.d.ts contain, how deterministic emission and content hashes work, and how the runtime and tooling consume the artifacts.'
+metaTitle: The Prisma ORM contract files
+metaDescription: 'What contract.json and contract.d.ts contain, why the same source always produces the same two files, how the hashes work, and how your app and the CLI read them.'
---
-Emitting your contract produces two generated files. This page explains what is in each, why they are deterministic, and how their hashes keep your code and your database in agreement.
+Your contract is [a single file that describes your data and how it is stored](/orm/contract-authoring/the-data-contract). It replaces the `schema.prisma` of Prisma ORM 7, and it is `src/prisma/contract.prisma` in Prisma Schema Language (PSL) or `src/prisma/contract.ts` in TypeScript. [`npx prisma contract emit`](/cli/contract-emit) reads that file and writes two files beside it. In the table below, `db` is your client, the object you run queries on. [`npx prisma orm init`](/cli/orm-init) creates the contract source, `src/prisma/db.ts`, and `.env.example` for you. In Prisma ORM 7 you edited `schema.prisma` and the tooling read the same file. Now you edit the contract and the tooling reads the two files it produces.
-[`contract emit`](/cli/contract-emit) writes both next to the source by default:
-
-| File | Contents | Consumed by |
+| File | Contents | Read by |
| --- | --- | --- |
-| `contract.json` | The canonical, machine-readable contract: models, storage, capabilities, and content hashes | The runtime, migration tooling, verification, and anything else that needs to read your schema |
+| `contract.json` | The machine-readable contract: models, how they are stored, which database features they need, and the hashes | `db`, `prisma db sign`, `prisma db verify`, and `prisma migration plan` |
| `contract.d.ts` | TypeScript declarations derived from the contract | The query APIs and your application code, for typed models and results |
-Both files are generated. Do not edit them; change the [PSL](/orm/contract-authoring/psl-syntax) or [TypeScript](/orm/contract-authoring/typescript-schema-builder) source and re-run `contract emit`. The files carry a generated-file notice that says exactly this.
+`prisma db sign` writes the current contract's hashes into the database, so later commands can tell which contract that database was built for. Both files are generated. Do not edit them. Change the [PSL](/orm/contract-authoring/psl-syntax) or [TypeScript](/orm/contract-authoring/typescript-schema-builder) source and run `npx prisma contract emit` again. `contract.d.ts` starts with a comment saying it is generated and must not be edited, and `contract.json` carries the same notice in a `_generated` entry. To write the two files to another directory, see [`prisma contract emit`](/cli/contract-emit).
-## Deterministic emission
+The everyday loop on a project:
-Emission is deterministic: the same source produces byte-identical artifacts on every machine and every run. Keys are written in canonical order and values in normalized form, which is what makes the artifacts diffable in code review and hashable for verification.
+1. Edit `contract.prisma` or `contract.ts`.
+2. Run `npx prisma contract emit`.
+3. Create the migration with [`npx prisma migration plan`](/cli/migration-plan), read it, then apply it with [`npx prisma db migrate`](/cli/db-migrate).
+4. Check the database against the contract with [`npx prisma db verify`](/cli/db-verify) in CI or in your deploy step.
-Determinism is also why the contract source must stay pure. A schema that read the clock or the environment would emit differently each time, and every hash-based guarantee below would collapse. The [TypeScript authoring page](/orm/contract-authoring/typescript-schema-builder#keep-the-contract-file-pure) lists the concrete rules.
+`npx prisma db verify` reports whether the database has a record and whether it matches. You almost never sign a database yourself:
-## The content hashes
+- Applying a migration records the match for you, and so does [`npx prisma db init`](/cli/db-init) when it creates the structures in an empty database.
+- Run [`npx prisma db sign`](/cli/db-sign) by hand only after [`npx prisma contract infer`](/cli/contract-infer) has written a starter contract from a database that already matches it.
-Emission computes hashes over distinct parts of the contract. Each answers a different question:
+## The same source always produces the same two files [#deterministic-emission]
-| Hash | Covers | Changes when |
-| --- | --- | --- |
-| `storageHash` | Models, fields, relations, and the full storage layout: tables, columns, keys, indexes | Any schema change |
-| `executionHash` | Defaults Prisma ORM applies before writes, such as `uuid()` generators | Generated defaults change |
-| `profileHash` | The profile the contract is built for: the target database and its family | The contract targets a different database |
+Run `npx prisma contract emit` on the same source on any machine and you get the same two files, byte for byte. That holds only if your contract source does not read the environment, the clock, or random values, and the [TypeScript authoring page](/orm/contract-authoring/typescript-schema-builder#keep-the-contract-file-pure) lists the rules.
-The hashes are how Prisma ORM connects the contract to a live database. [`db sign`](/cli/db-sign) checks that the database satisfies the contract and records the hashes in a marker inside the database. From then on, [`db verify`](/cli/db-verify) and the runtime compare the contract they hold against the marker, so a stale deploy or an unmigrated database is caught by verification rather than by a failing query.
+## The content hashes
+
+`npx prisma contract emit` computes a hash over each part of the contract. You never compute or compare them yourself. `storageHash` covers the storage layout: tables, columns, primary keys, uniques, indexes, foreign keys, and the allowed values of each enum. `profileHash` covers which database the contract targets. The hashes are how Prisma ORM connects the contract to a live database. `npx prisma db sign` checks that the database satisfies the contract, then records `storageHash` and `profileHash` in the database. `npx prisma db verify` compares the contract you hold against that record and against the live schema, and fails when they disagree. To fix a mismatch, apply the pending migration, or run `npx prisma db sign` when the database already matches the contract.
## Inside `contract.json`
-The contract separates what your application models from how it is stored. The `domain` section describes models, fields, and relations; the `storage` section describes tables, columns, keys, and indexes (or collections and indexes for a MongoDB contract); each model's `storage` block bridges the two. Everything is grouped by namespace (on PostgreSQL, the schema, typically `public`).
+The contract keeps your application's view of the data separate from the way the database stores it. The `domain` section describes models, fields, and relations. The `storage` section describes tables, columns, keys, and indexes. On MongoDB it describes collections and indexes instead. Each model's own `storage` block maps each field to its column. Everything is grouped by namespace. On PostgreSQL the namespace is the schema. It is `public` unless you put the models in a `namespace` block in your [PSL source](/orm/contract-authoring/psl-syntax).
-An abridged emit of a `User`/`Post` schema:
+An abridged `contract.json` for a `User` and `Post` schema:
-```json title="prisma/contract.json (abridged)"
+```json title="src/prisma/contract.json (abridged)"
{
"schemaVersion": "1",
"targetFamily": "sql",
"target": "postgres",
"profileHash": "…",
"roots": {
- "user": { "namespace": "public", "model": "User" },
- "post": { "namespace": "public", "model": "Post" }
+ "post": { "model": "Post", "namespace": "public" },
+ "user": { "model": "User", "namespace": "public" }
},
"domain": {
"namespaces": {
@@ -57,20 +57,13 @@ An abridged emit of a `User`/`Post` schema:
"models": {
"User": {
"fields": {
- "id": { "nullable": false, "type": { "kind": "scalar", "codecId": "pg/uuid@1" } },
- "email": { "nullable": false, "type": { "kind": "scalar", "codecId": "pg/text@1" } }
- },
- "relations": {
- "posts": {
- "cardinality": "1:N",
- "to": { "namespace": "public", "model": "Post" },
- "on": { "localFields": ["id"], "targetFields": ["userId"] }
- }
+ "email": { "nullable": false, "type": { "codecId": "pg/text@1", "kind": "scalar" } },
+ "id": { "nullable": false, "type": { "codecId": "pg/uuid@1", "kind": "scalar" } }
},
"storage": {
+ "fields": { "email": { "column": "email" }, "id": { "column": "id" } },
"namespaceId": "public",
- "table": "user",
- "fields": { "id": { "column": "id" }, "email": { "column": "email" } }
+ "table": "user"
}
}
}
@@ -78,88 +71,88 @@ An abridged emit of a `User`/`Post` schema:
}
},
"storage": {
- "storageHash": "…",
"namespaces": {
"public": {
"entries": {
"table": {
"user": {
"columns": {
- "id": { "nativeType": "uuid", "codecId": "pg/uuid@1", "nullable": false },
- "email": { "nativeType": "text", "codecId": "pg/text@1", "nullable": false }
+ "email": { "codecId": "pg/text@1", "nativeType": "text", "nullable": false },
+ "id": { "codecId": "pg/uuid@1", "nativeType": "uuid", "nullable": false }
},
"primaryKey": { "columns": ["id"] }
}
}
}
}
- }
+ },
+ "storageHash": "…"
},
- "execution": { "executionHash": "…" },
- "capabilities": { "postgres": { "returning": true } },
- "extensions": {}
+ "capabilities": { "postgres": { "returning": true } }
}
```
The sections, top to bottom:
-- **`schemaVersion`, `targetFamily`, `target`**: the contract format version and the database this contract targets.
-- **`roots`**: the accessor names your queries start from, each mapping to a model. The SQL builder's `db.sql.public.user` exists because `roots.user` points at `User`, and the model it names is what the ORM accessor `db.orm.public.User` hangs off.
-- **`domain`**: the application's view. Each field carries `nullable` and a `codecId` such as `pg/text@1`, which names the codec that encodes and decodes values of that type. Relations record cardinality and the fields they join on. The model's `storage` block maps fields to columns.
-- **`storage`**: the database's view: tables with columns (native type plus codec), primary keys, uniques, indexes, foreign keys, and value sets backing enums. This is the section migration tooling diffs and `db verify` checks the live schema against.
-- **`execution`**: defaults Prisma ORM applies before writes, such as UUID generation, kept out of the database's DDL.
-- **`capabilities`** and **`extensions`**: the database and extension features available to this contract, merged from the target, adapter, and extension packs at emit time. Query APIs consult these before using gated features; see [Capabilities](/orm/contract-authoring/capabilities).
+- **`schemaVersion`, `targetFamily`, `target`**: the contract format version, whether the database is SQL or MongoDB, and which database this contract targets.
+- **`roots`**: one entry per table that stores a model. The key is the name of that table, so for a model `User` mapped to a table `users` the key is `users`. The value names the model. The [ORM reference](/orm/reference) covers both ways of reaching it, `db.sql.public.user` by table name and `db.orm.public.User` by model name.
+- **`domain` and `storage`**: your application's view, then the database's view. Each scalar field in `domain` records whether it is nullable and its `codecId`. `nativeType` is the column type in the database. `codecId` names how Prisma ORM reads and writes the value, the PostgreSQL type plus a version. `storage` holds the tables with their columns, keys, indexes, constraints, and enum values. `prisma db verify` compares it to the live database.
+- **`capabilities`**: which database features this contract can use. `npx prisma contract emit` collects them from the adapter for your database and from the packages listed in `extensions: [...]` in `prisma.config.ts`, the config file `prisma orm init` writes. The query APIs check them before building a query that needs one. See [supported database features](/orm/contract-authoring/capabilities) and [extension types](/orm/contract-authoring/psl-syntax#extension-types).
## Inside `contract.d.ts`
-The declarations file gives the type system the same information. It exports the contract type, branded hash types matching `contract.json`, and input/output types for every model:
+The declarations file gives the type system the same information. It exports:
+
+- `Contract`, the type the client is built from. `db.ts` imports it.
+- One type per hash, such as `StorageHash` and `ProfileHash`. Each one carries the hash of that part of the contract.
+- Input and output types for each [`type` block](/orm/contract-authoring/psl-syntax#value-objects) in your contract.
-```typescript title="prisma/contract.d.ts (excerpt)"
+```typescript title="src/prisma/contract.d.ts (excerpt)"
export type StorageHash =
- StorageHashBase<"9f49f8f9e51a9cc016f1ec2098ebae9406521a3cc2cf00207adc795078333d8b">;
-export type ProfileHash =
- ProfileHashBase<"9c8aa3114e84ed3b7ea2bd57526d9c2e1bf7c5292be694e9d3801f566fda7ccb">;
-
-export type AddressOutput = {
- readonly street: CodecTypes["pg/text@1"]["output"];
- readonly city: CodecTypes["pg/text@1"]["output"];
- readonly zip: CodecTypes["pg/text@1"]["output"] | null;
- readonly country: CodecTypes["pg/text@1"]["output"];
-};
+ StorageHashBase<'9f49f8f9e51a9cc016f1ec2098ebae9406521a3cc2cf00207adc795078333d8b'>;
```
-Because the hashes are literal types, a client built against one contract version is type-incompatible with another version's artifacts. Type checking catches a mismatched contract before verification has to.
+You never write these types yourself. Both files come from the one `npx prisma contract emit` run, so they only drift when someone commits one and not the other. The CI check below catches that.
## How the application consumes the artifacts
-The runtime client is constructed from both files: `contract.json` as the value, `contract.d.ts` as the type.
+`db` is built from both files: `contract.json` as the value, `contract.d.ts` as the type. `prisma orm init` writes `src/prisma/db.ts` for you.
```typescript title="src/prisma/db.ts"
+import "dotenv/config";
import postgres from "@prisma/orm-postgres/runtime";
import type { Contract } from "./contract.d";
import contractJson from "./contract.json" with { type: "json" };
export const db = postgres({
contractJson,
+ url: process.env['DATABASE_URL']!,
});
```
-At startup, the client carries the contract's hashes and verifies them against the database marker before executing queries.
+The connection string comes from the `DATABASE_URL` environment variable. `prisma orm init` writes `.env.example`. Put your `DATABASE_URL` in `.env`. Keep the `.d` in the import path. Delete the `.d` and the import stops resolving. The `tsconfig.json` settings the JSON import needs are on the [transactions and runtime page](/orm/reference/transactions-and-runtime). Before its first query, `db` reads the record in the database of which contract it matches and compares it with the contract's hashes. If they do not match, it logs a warning whose `code` is `CONTRACT.MARKER_MISMATCH` and runs the query anyway. If there is no record at all, because nothing has signed the database yet, it logs a warning whose `code` is `CONTRACT.MARKER_MISSING` and again runs the query. On MongoDB the client is `mongo(...)` from `@prisma/orm-mongo/runtime`, described under [`mongo(options)`](/orm/reference/transactions-and-runtime#mongooptions).
+
+When `CONTRACT.MARKER_MISMATCH` shows up in production, apply the pending migration, or run `npx prisma db sign` when the database already matches the contract. The client warns by default. Set [`verifyMarker`](/orm/reference/transactions-and-runtime#postgresoptions) to `false` to skip the check. It cannot be made to fail. Run `prisma db verify` when you need a check that fails.
## Version control
-Commit the artifacts alongside the source. They contain structure only, no data and no credentials, and committed artifacts let teammates, CI, and deploys consume the contract without re-running emission. Re-run `contract emit` after every source change so the committed artifacts never trail the source; a CI job can enforce this by running the emit and failing if the working tree changes.
+Commit `contract.json` and `contract.d.ts` alongside the source. They hold structure only, no data and no credentials. Committing them lets teammates, CI, and deploys read the contract without running `npx prisma contract emit` first. Run `npx prisma contract emit` after every source change so the two files never trail the source. A CI job can check this in two lines:
+
+```bash
+npx prisma contract emit
+git diff --exit-code src/prisma
+```
## Prompt your coding agent
-Projects scaffolded with `create-prisma@latest` install [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent; the `prisma-8` skill covers this page. Ask your agent to:
+Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. In an existing project, run `npx prisma skills sync`. The `prisma-8` skill covers the contract. Ask your agent to:
- "Explain the difference between contract.json and contract.d.ts in this project."
-- "Re-emit the contract and show me what changed in the artifact."
+- "Run prisma contract emit and show me what changed in contract.json."
## Next steps
-- Learn how the contract's [capabilities](/orm/contract-authoring/capabilities) gate database features.
-- Apply the contract to a fresh database with [`db init`](/cli/db-init).
-- Check a live database against the contract with [`db verify`](/cli/db-verify).
-- Plan schema changes between contract versions with [`migration plan`](/cli/migration-plan).
+- Learn [which database features](/orm/contract-authoring/capabilities) the contract needs, and how Prisma ORM checks that your database has them.
+- Apply the contract to a fresh database with [`prisma db init`](/cli/db-init).
+- Check a live database against the contract with [`prisma db verify`](/cli/db-verify).
+- Plan schema changes between contract versions with [`prisma migration plan`](/cli/migration-plan).
diff --git a/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx b/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx
index 570dd9d7fb7..83711263ae5 100644
--- a/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx
+++ b/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx
@@ -1,16 +1,14 @@
---
title: The data contract
-description: 'The data contract is the single description of your data model and its storage layout. Everything in Prisma ORM is typed, planned, and verified against it.'
+description: 'The data contract is the one description of your data model and how it is stored. Prisma ORM types your queries, plans your migrations, and checks your database against it.'
url: /orm/contract-authoring/the-data-contract
metaTitle: The Prisma ORM data contract
-metaDescription: 'What the Prisma ORM data contract is, how PSL and TypeScript authoring produce the same artifacts, and how the contract drives typing, migrations, and verification.'
+metaDescription: 'What the Prisma ORM data contract is, how PSL and TypeScript authoring produce the same two files, and how the contract drives typing, migrations, and checking your database.'
---
-Every Prisma ORM project has one description of its data: the models, their fields, and how they map to database tables. That description is the data contract.
+Every Prisma ORM project has one description of its data: the models, their fields, and how they map to database tables. That description is the data contract, the `contract.prisma` file that replaced `schema.prisma`. For example, a blog's contract declares a `User` and a `Post`, the fields each one has, and how they relate. You author it in PSL, the Prisma Schema Language, the same language as `schema.prisma`:
-For example, a blog's contract declares a `User` and a `Post`, the fields each carries, and how they relate. You author it in PSL, the Prisma schema language:
-
-```prisma title="prisma/contract.prisma"
+```prisma title="src/prisma/contract.prisma"
// use prisma-8
model User {
@@ -23,92 +21,94 @@ model Post {
id Int @id @default(autoincrement())
title String
userId Int
-
- user User @relation(fields: [userId], references: [id])
+ user User @relation(fields: [userId], references: [id])
}
```
-The first line of every `.prisma` contract is `// use prisma-8`. It marks the file as a Prisma ORM 8 contract rather than a Prisma ORM 7 schema: the Prisma language server only offers diagnostics, completion, and formatting on files that carry it, and excludes unmarked files from its schema composition. Completion covers field, model, and block attribute names and their argument keys, taken from the installed target and extensions. Contracts written before `8.0.0-rc.10` start with `// use prisma-next`; the language server still accepts that line and replaces it when you format the file.
-
-Prisma ORM compiles this into a machine-readable artifact, and the rest of the toolchain works against it: queries are type-checked against the contract, migrations are planned as changes to it, and the database is verified against it before your code runs.
+`npx prisma orm init` writes the first line, `// use prisma-8`, into every `.prisma` contract. The CLI does not need this line. The VS Code extension does, so keep it. A `contract.ts` file needs no header line.
-Think of it as a `package-lock.json` for your data: an exact, versioned record of what your application expects from its database.
+`npx prisma contract emit` turns this file into two files that the CLI and your application read: `contract.json` and `contract.d.ts`. Your queries are type-checked against the contract, migrations are planned as changes to it, and `npx prisma db verify` checks a live database against it.
:::note[Contract vs. schema]
-In Prisma ORM, the **contract** is what you write in your code, and the **schema** is your database's actual structure. Some tools use these words the other way around, so keep the distinction in mind: you author a contract, and Prisma ORM checks that the database's schema satisfies it.
+In Prisma ORM, the **contract** is what you write in your code. The **schema** is your database's actual structure.
:::
## Why a contract
-Prisma ORM's current architecture compiles your schema into generated client code. The schema knowledge exists, but it is buried in code that only the client itself can interpret.
+Your contract compiles to two plain files you can open and read. `contract.json` describes your models, how they are stored, and the [database features they need](/orm/contract-authoring/capabilities). `contract.d.ts` holds the TypeScript types derived from it. You can read both files in a code review and hand them to tools and coding agents.
-Prisma ORM keeps the schema knowledge in the open. Your contract source compiles to two plain files: `contract.json`, a canonical JSON description of models, storage, and capabilities, and `contract.d.ts`, the TypeScript types derived from it. Both are deterministic: the same source always produces byte-identical output, so the artifacts can be diffed in code review, hashed for verification, and read directly by tools and AI agents.
-
-The contract also identifies the exact schema version it describes. When you emit the contract, Prisma ORM computes hashes over its content, and [`db sign`](/cli/db-sign) records them in a small marker inside the database. Before executing queries, Prisma ORM compares the contract it was built against with the marker in the database it is talking to. If they do not match, for example after a deploy against an unmigrated database, verification fails before any query runs.
+`npx prisma db migrate` records in your database which contract it applied. The first time `db` runs a query it checks that record and logs a warning on a mismatch. To fail instead, run [`npx prisma db verify`](/cli/db-verify) in a deploy check.
## How it works
-A Prisma ORM project declares one contract source in `prisma.config.ts`:
+A Prisma ORM project declares one contract file in `prisma.config.ts`:
```typescript title="prisma.config.ts"
+import "dotenv/config";
import { definePrismaConfig } from "prisma/config";
import { defineConfig as ormConfig } from "@prisma/orm-postgres/config";
export default definePrismaConfig({
orm: ormConfig({
- contract: "./prisma/contract.prisma",
+ contract: "./src/prisma/contract.prisma",
+ db: {
+ connection: process.env["DATABASE_URL"]!,
+ },
}),
});
```
-The source is either a Prisma schema file (`contract.prisma`) or a TypeScript file (`contract.ts`). The file extension selects the authoring mode. Both modes describe the same things: models with fields and relations, the tables and columns they map to, named types, enums, and any extension-provided types.
+`definePrismaConfig` holds the whole config: which contract file to use and how to reach your database. Your connection string is in `prisma.config.ts` now, and the contract has no `datasource` block. `npx prisma orm init` writes `.env.example`, and your `DATABASE_URL` goes in `.env`. `@prisma/orm-postgres/config` exports `defineConfig`. The example renames it to `ormConfig` on import. The packages are `@prisma/orm-postgres` for PostgreSQL, `@prisma/orm-sqlite` for SQLite, and `@prisma/orm-mongo` for MongoDB. The examples here use PostgreSQL.
+
+To start a new project, run `npm create prisma@latest -- my-app`. To add Prisma ORM 8 to a project that has no Prisma ORM at all, run `npx prisma orm init`. If you already have a Prisma ORM 7 `schema.prisma`, follow the [PostgreSQL upgrade guide](/guides/upgrade-prisma-orm/postgresql) instead, and [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7) lists what changed. Both `npm create prisma@latest` and `npx prisma orm init` write `prisma.config.ts`, the contract file, and `src/prisma/db.ts`, and install the database package.
+
+The contract file is either a PSL file (`contract.prisma`) or a TypeScript file (`contract.ts`). The file extension selects the authoring mode, so to author in TypeScript you point `contract` at `./src/prisma/contract.ts`. Both modes describe the same things: models with fields and relations, the tables and columns they map to, named types, enums, and any types that [extension packages](/orm/contract-authoring/psl-syntax#extension-types) add. Extension packages are npm packages that add field types and database features, such as pgvector, a PostgreSQL extension for vector columns.
-Emitting turns the source into the artifacts:
+After every change to the contract, run these three commands:
```npm
-npx prisma@latest contract emit
+npx prisma contract emit
+npx prisma migration plan
+npx prisma db migrate
```
-This writes `contract.json` and `contract.d.ts` next to the source. From there, the rest of the toolchain takes over:
+`npx prisma contract emit` writes `contract.json` and `contract.d.ts` beside the contract file, so `src/prisma/`. [`npx prisma migration plan`](/orm/migrations/generating-a-migration) compares your contract against the last one and writes a migration. [`npx prisma db migrate`](/orm/migrations/applying-a-migration) applies it and records the match for you. When the database already matches the contract, run [`npx prisma db sign`](/cli/db-sign), which records the match without applying anything, for example after [`npx prisma contract infer`](/cli/contract-infer), which writes a contract from an existing database.
-- The query APIs load `contract.d.ts` to give you typed models, fields, and results.
-- The runtime receives `contract.json` and verifies its hashes against the database marker.
-- The migration tooling diffs two contracts to plan schema changes, and [`db verify`](/cli/db-verify) checks a live database against the current contract.
+`db` is the client you create once in `src/prisma/db.ts`, and `npx prisma orm init` writes that file for you. From a file directly inside `src/`, import it with `import { db } from "./prisma/db"` and query a model: `await db.orm.public.User.all()` returns every user row. From a deeper file, adjust the relative path. `db.orm` is the ORM client, one of the [three query APIs](/orm/reference), and `public` is the PostgreSQL schema, the namespace your tables are in, unless you configured a different one. [Reading data](/orm/fundamentals/reading-data) covers queries.
-## Two authoring modes, one artifact
+## Two authoring modes, one `contract.json` [#two-authoring-modes-one-artifact]
-**[PSL](/orm/contract-authoring/psl-syntax)** is the preferred way to author the contract: a compact language purpose-built for describing data. It is what `create-prisma` scaffolds, what [`contract infer`](/cli/contract-infer) writes when you start from an existing database, and what the examples throughout these docs use.
+**[PSL](/orm/contract-authoring/psl-syntax)** is the usual way to write the contract. It is a compact language for describing data. It is what `npm create prisma@latest` scaffolds, and what [`npx prisma contract infer`](/cli/contract-infer) writes when you start from an existing database.
-For the cases PSL does not cover, define models with the **[TypeScript builder](/orm/contract-authoring/typescript-schema-builder)** instead. Reach for it when model definitions must be composed, generated, or shared as ordinary TypeScript modules.
+Define models with the **[TypeScript builder](/orm/contract-authoring/typescript-schema-builder)** instead when you want to build them with code. Reach for it when model definitions must be composed, generated, or shared as ordinary TypeScript modules. That page shows what a `contract.ts` file looks like.
-Both modes produce the same contract. For an equivalent schema they emit the same `contract.json`, so migrations, verification, and the query APIs behave the same no matter which mode you write. You give up nothing by staying with PSL. A project declares exactly one source of truth: the file named by `contract` in the config. Keep the other form out of the project, or treat it as a generated reference, so the two can never disagree.
+Both modes produce the same contract, so migrations, database checks, and `db` behave the same no matter which mode you write. A project has exactly one contract file. Do not keep both a `contract.prisma` and a `contract.ts`: the config names one file and Prisma ORM reads only that one, so edits to the other file do nothing. To switch modes, change the path in `contract` and delete the old file.
## What the contract contains
The contract describes structure, not data:
- models, fields, and relations, plus how they map to tables and columns
-- storage details: primary keys, unique constraints, indexes, and foreign keys
-- named types, enums, and value objects
-- types and capabilities contributed by extension packs, such as pgvector's `Vector`
-- content hashes that identify this exact version of the schema
+- how the data is stored: primary keys, unique constraints, indexes, and foreign keys
+- [named types](/orm/contract-authoring/psl-syntax#named-types), which are reusable aliases for a database column type, and enums
+- [`type` blocks](/orm/contract-authoring/psl-syntax#value-objects), also called value objects, each a reusable group of fields such as an address, stored inside its parent row with no table of its own
+- the types and database features that extension packages add, such as pgvector's `Vector` type
-It contains no rows, no credentials, and no connection details, so committing the source and the emitted artifacts to version control is safe and expected.
+It contains no rows, no credentials, and no connection details, so committing the source, `contract.json`, and `contract.d.ts` to version control is safe and expected.
## Prompt your coding agent
-Projects scaffolded with `create-prisma@latest` install [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. The `prisma-8` skill covers this page. Ask your agent to:
+Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. In an existing project, run `npx prisma skills sync`. The `prisma-8` skill covers the contract. Ask your agent to:
- "Using the prisma-8 skill, explain what our contract.json currently declares."
-- "Add an Invoice model to the contract and emit it."
+- "Add an Invoice model to the contract and run prisma contract emit."
- "Check whether our database still satisfies the contract."
## Next steps
- [Model your data](/orm/data-modeling) before writing the contract: models, keys, and relations.
-- [Query against the contract](/orm/fundamentals/reading-data): every result is typed by what you authored here.
}
>
- Write the contract as a Prisma schema file.
+ Write the contract as a PSL file.
}
>
- Define the same models with the typed contract builder.
+ Define the same models with the TypeScript builder.
}
>
What is inside contract.json and contract.d.ts, and how the hashes work.
}
>
- How Prisma ORM checks that your database supports what the contract
- needs.
+ How Prisma ORM checks that your database supports what the contract needs.
diff --git a/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx b/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx
index 0fd5cc18bef..e94aa77ec36 100644
--- a/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx
+++ b/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx
@@ -1,25 +1,27 @@
---
title: Author in TypeScript
-description: 'Define the Prisma ORM contract with a typed builder API instead of a schema file. Same models, same artifacts, no separate language.'
+description: 'Define the Prisma ORM contract with a typed builder in TypeScript instead of a schema file. Same models, same `contract.json` and `contract.d.ts`, no separate language.'
url: /orm/contract-authoring/typescript-schema-builder
metaTitle: Author the Prisma ORM contract in TypeScript
-metaDescription: 'Learn how to define a Prisma ORM contract in TypeScript with defineContract, including fields, relations, enums, storage mapping, and extension types.'
+metaDescription: 'How to define a Prisma ORM contract in TypeScript with defineContract, including fields, relations, enums, storage mapping, and extension types.'
---
-With TypeScript authoring, you define the Prisma ORM [data contract](/orm/contract-authoring/the-data-contract) in code. Instead of a `.prisma` file, you write `prisma/contract.ts` with the `defineContract` builder, and [`contract emit`](/cli/contract-emit) produces exactly the same `contract.json` and `contract.d.ts` a PSL schema would.
+In Prisma ORM 8, `schema.prisma` is replaced by a file called [the contract](/orm/contract-authoring/the-data-contract). It holds the model definitions you used to write in `schema.prisma`. You can write it in two forms. One is a `.prisma` file written in PSL, short for Prisma Schema Language. The other is TypeScript, in `src/prisma/contract.ts`, built with the `defineContract` builder. Both forms produce the same two files. [`npx prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` into the folder that holds your contract file.
## When to choose TypeScript over PSL
-[PSL](/orm/contract-authoring/psl-syntax) is the preferred way to author the contract. Both modes emit identical artifacts, so you give up nothing by staying with PSL. The TypeScript builder is an escape hatch for the cases PSL does not cover. Reach for it when:
+[PSL](/orm/contract-authoring/psl-syntax) is the preferred way to write the contract. Both forms produce the same two files, so you give up nothing by staying with PSL. Use the TypeScript builder for the cases PSL does not cover. Reach for it when:
- model definitions must be split, composed, or reused across ordinary TypeScript modules or packages
-- parts of the schema are assembled programmatically from other static definitions (the [purity rules](#keep-the-contract-file-pure) still apply)
+- you want to build models in a loop from data you already keep in TypeScript, such as one model per entry in a list of table names
-If neither applies, write PSL: it is more compact, and it is what `create-prisma` scaffolds and [`contract infer`](/cli/contract-infer) writes.
+If neither applies, write PSL. It is more compact, and it is what [`contract infer`](/cli/contract-infer) writes.
## Point the config at the contract file
-The config's `contract` path names the source of truth. A `.ts` extension selects TypeScript authoring:
+For a new project, run [`npx prisma orm init`](/cli/orm-init). It asks how you want to write your schema, and choosing TypeScript creates the contract file and the config together.
+
+The config's `contract` path names the one file Prisma ORM reads. A `.ts` extension selects TypeScript authoring:
```typescript title="prisma.config.ts" tab="PostgreSQL"
import { definePrismaConfig } from "prisma/config";
@@ -27,7 +29,7 @@ import { defineConfig as ormConfig } from "@prisma/orm-postgres/config";
export default definePrismaConfig({
orm: ormConfig({
- contract: "./prisma/contract.ts",
+ contract: "./src/prisma/contract.ts",
}),
});
```
@@ -38,95 +40,66 @@ import { defineConfig as ormConfig } from "@prisma/orm-mongo/config";
export default definePrismaConfig({
orm: ormConfig({
- contract: "./prisma/contract.ts",
+ contract: "./src/prisma/contract.ts",
}),
});
```
## A complete contract
-The builder comes from your database's target package: `@prisma/orm-postgres/contract-builder` for PostgreSQL, `@prisma/orm-mongo/contract-builder` for MongoDB.
+The builder comes from your database's package: `@prisma/orm-postgres/contract-builder` for PostgreSQL, `@prisma/orm-mongo/contract-builder` for MongoDB.
-```typescript title="prisma/contract.ts" tab="PostgreSQL"
-import pgvector from "@prisma/orm-extension-pgvector/pack";
-import { defineContract, enumType, member, rel } from "@prisma/orm-postgres/contract-builder";
+Export the contract under the name `contract`, as below, or as the file's default export. Those are the only two names `prisma contract emit` looks for. Run `npx prisma contract emit` again after every edit to the contract, and commit the contract file together with [`contract.json` and `contract.d.ts`](/orm/contract-authoring/the-contract-artifact).
+```typescript title="src/prisma/contract.ts" tab="PostgreSQL"
+import { defineContract, enumType, member } from "@prisma/orm-postgres/contract-builder";
+
+// nativeType is the column type the database gets. codecId picks how Prisma ORM converts the value between TypeScript and that column, and its @1 is the version of that conversion.
const pgText = { codecId: "pg/text@1", nativeType: "text" } as const;
-const Priority = enumType(
- "Priority",
- pgText,
- member("Low", "low"),
- member("High", "high"),
- member("Urgent", "urgent"),
-);
-
-export const contract = defineContract(
- {
- extensions: { pgvector },
- },
- ({ field, model, type }) => {
- const types = {
- Embedding1536: type.pgvector.Vector(1536),
- } as const;
-
- const User = model("User", {
- fields: {
- id: field.id.uuidv4String(),
- email: field.text(),
- createdAt: field.temporal.createdAt(),
- updatedAt: field.temporal.updatedAt(),
- address: field.json().optional(),
- },
- });
-
- const Post = model("Post", {
- fields: {
- id: field.id.uuidv4String(),
- title: field.text(),
- userId: field.uuidString(),
- priority: field.namedType(Priority).default(Priority.members.Low),
- createdAt: field.temporal.createdAt(),
- updatedAt: field.temporal.updatedAt(),
- embedding: field.namedType(types.Embedding1536).optional(),
- },
- });
-
- return {
- enums: { Priority },
- types,
- models: {
- User: User.relations({
- posts: rel.hasMany(Post, { by: "userId" }),
- }).sql({
- table: "user",
- }),
- Post: Post.relations({
- user: rel.belongsTo(User, { from: "userId", to: "id" }),
- }).sql(({ cols, constraints }) => ({
- table: "post",
- foreignKeys: [
- constraints.foreignKey(cols.userId, User.refs.id, {
- name: "post_userId_fkey",
- }),
- ],
- })),
- },
- };
- },
-);
+const Priority = enumType("Priority", pgText, member("Low", "low"), member("High", "high"));
+
+export const contract = defineContract({}, ({ field, model, rel }) => {
+ const User = model("User", {
+ fields: {
+ id: field.id.uuidv4String(),
+ email: field.text(),
+ createdAt: field.temporal.createdAt(),
+ address: field.json().optional(),
+ },
+ });
+
+ const Post = model("Post", {
+ fields: {
+ id: field.id.uuidv4String(),
+ title: field.text(),
+ userId: field.uuidString(),
+ priority: field.namedType(Priority).default(Priority.members.Low),
+ createdAt: field.temporal.createdAt(),
+ updatedAt: field.temporal.updatedAt(),
+ },
+ });
+
+ return {
+ enums: { Priority },
+ models: {
+ User: User.relations({ posts: rel.hasMany(Post, { by: "userId" }) }).sql({ table: "user" }),
+ Post: Post.relations({
+ user: rel.belongsTo(User, { from: "userId", to: "id" }).sql({ fk: { name: "post_userId_fkey" } }),
+ }).sql({ table: "post" }),
+ },
+ };
+});
```
-```typescript title="prisma/contract.ts" tab="MongoDB"
+```typescript title="src/prisma/contract.ts" tab="MongoDB"
import { defineContract, field, model, rel } from "@prisma/orm-mongo/contract-builder";
const User = model("User", {
collection: "users",
fields: {
_id: field.objectId(),
- name: field.string(),
email: field.string(),
- bio: field.string().optional(),
},
relations: {
posts: rel.hasMany("Post", { from: "_id", to: "authorId" }),
@@ -146,137 +119,200 @@ const Post = model("Post", {
},
});
-export const contract = defineContract({
- models: {
- User,
- Post,
- },
-});
+export const contract = defineContract({ models: { User, Post } });
```
-Run `npx prisma@latest contract emit` after any change to refresh the artifacts.
+## How `defineContract` works
-The two builders share the same shape but differ where the databases do. On PostgreSQL, fields pick column types and models chain `.sql({ table })` to map storage. On MongoDB, the ID is a `field.objectId()` named `_id`, and the scalar helpers are `field.string()`, `field.int32()`, `field.double()`, `field.bool()`, and `field.date()`. The collection is an inline `collection` option on the model rather than a chained call.
+On PostgreSQL, `defineContract` takes an options object and then a function. You do not set a `provider` anywhere. Importing `@prisma/orm-postgres/contract-builder` is what selects PostgreSQL. The options object lists the extension packs you use, and you write `{}` when you use none. The function returns the contract's content: `models`, plus `enums` and `types` if you have them.
-## How `defineContract` works
+Take `field`, `model`, `rel`, and `type` from the function's one argument. Import `defineContract`, `enumType`, and `member` from the package. The package also exports `model` and `rel` for use outside the function, plus a `field` that has only `column`, `generated`, and `namedType`.
-`defineContract` takes two arguments: an options object and a factory function.
+On MongoDB, `defineContract` also accepts a single object holding `models`, as above, and you import `field`, `model`, and `rel` from the package instead. The MongoDB builder differs from the PostgreSQL one in five ways:
-The options object declares what the contract is built from, most importantly `extensions`. The target and database family are already bound by the import: `@prisma/orm-postgres/contract-builder` produces PostgreSQL contracts, so you never name them yourself.
+- Every model declares an `_id` field built with `field.objectId()`. That field is always the primary key, so you never mark it with `.id()` and there is no `.attributes(...)` on MongoDB.
+- The scalar helpers are `field.objectId()`, `field.string()`, `field.int32()`, `field.double()`, `field.bool()`, and `field.date()`.
+- The collection name is an inline option on the model rather than a chained call, and relations are inline as well.
+- Relations name the field on each side. `rel.hasMany` takes `{ from, to }` on MongoDB and `{ by }` on PostgreSQL. `to` accepts either the field name as a string or a typed reference such as `User.ref("_id")`, and both forms mean the same thing. PostgreSQL writes that reference as `User.refs.id`.
+- Indexes are an `indexes` option on the model, built with the `index` helper the same package exports, as in `indexes: [index({ email: 1 }, { unique: true })]`. The `1` is ascending, MongoDB's own index syntax.
-The factory receives authoring helpers composed from the target and every extension pack you declared: `field` for field definitions, `model` for models, and `type` for pack-provided types. `type.pgvector` in the example exists only because `pgvector` is listed in `extensions`. The factory returns the contract's content: `models`, plus optional `enums` and `types`.
+Enums and extension packs work on MongoDB too. `enumType` and `member` come from `@prisma/orm-mongo/contract-builder`. Pass the enums in the same object as the models, as `defineContract({ models, enums })`, and extension packs go in the same `extensions` option.
-The MongoDB builder is simpler: it exports `field`, `model`, and `rel` directly, and its `defineContract` takes a single definition object with the `models`, as in the MongoDB tab above. The sections below use the PostgreSQL builder.
+The examples below use the PostgreSQL builder.
## Fields
-`field` provides typed constructors for common field shapes:
+`field` has a helper for each column type. On PostgreSQL:
+
+| Column | Helper |
+| --- | --- |
+| text | `field.text()` |
+| integer | `field.int()` |
+| big integer | `field.bigint()` |
+| float | `field.float()` |
+| decimal | `field.decimal()` |
+| boolean | `field.boolean()` |
+| date and time | `field.dateTime()` |
+| bytes | `field.bytes()` |
+| JSON | `field.json()` |
+| UUID stored in a `character(36)` column | `field.uuidString()` |
+| UUID stored in a `uuid` column | `field.uuidNative()` |
+
+There is no helper for a date without a time, or a time without a date. For a date-only column, name the type yourself: `field.column({ codecId: "pg/date-temporal@1", nativeType: "date" } as const)`. `field.column(...)` and the chained `.column(...)` below are different calls: `field.column(descriptor)` builds a field from a type description, and `.column(name)` on an existing field sets its column name. Extension packs add more helpers of their own.
+
+For a primary key whose value your app generates, use `field.id.uuidv4String()`, `field.id.uuidv7String()`, `field.id.ulid()`, `field.id.nanoid()`, `field.id.cuid2()`, or `field.id.ksuid()`. They all generate the ID in your app, and `uuidv4String` is the common choice. Each marks the field as the primary key for you. `field.id.uuidv4Native()` and `field.id.uuidv7Native()` do the same in a `uuid` column. There is no auto-increment helper, so use one of those `field.id.*` helpers. For a key the database generates instead, write `field.uuidNative().defaultSql("gen_random_uuid()").id()`.
-- `field.text()`, `field.uuidString()`, `field.json()` for scalar fields
-- `field.id.uuidv4String()` for a UUID primary key with a client-generated default
-- `field.temporal.createdAt()` and `field.temporal.updatedAt()` for managed timestamps
-- `field.namedType(x)` for enums and declared named types
+`field.temporal.createdAt()` fills the column with `now()` when the row is created. `field.temporal.updatedAt()` fills it on every create and every update, and Prisma ORM sets it in the client, not with a database trigger. `field.namedType(x)` takes an enum or a type from an extension pack.
-The exact helper set comes from the target and the composed extension packs. Every field builder supports chained modifiers:
+Every field builder supports chained modifiers:
- `.optional()` makes the field nullable.
-- `.default(value)` sets a literal default; `.defaultSql(expression)` sets a database function default.
-- `.unique()` adds a unique constraint; `.id()` marks the primary key.
-- `.column("column_name")` sets the physical column name when it differs from the field name.
+- `.default(value)` sets a literal default. `.defaultSql(expression)` sets a default the database computes, and the argument is a SQL expression as a string: `.defaultSql("now()")`.
+- `.unique()` adds a unique constraint. `.id()` marks the primary key, for a key that is not generated, such as an integer you set yourself.
+- `.column("column_name")` sets the column name in the database when it differs from the field name.
## Enums
-`enumType` declares an enum with an explicit storage codec and members:
+`enumType` declares an enum, the type its values are stored as, and its members, as `Priority` does in the full example above.
+
+The second argument says how each member is stored, as the same `codecId` and `nativeType` pair the full example above explains. Write `as const` after the object, so TypeScript keeps the exact strings. To store the members as integers, pass `{ codecId: "pg/int4@1", nativeType: "int4" } as const`. More type ids are listed with the [raw query `param` helper](/orm/reference/raw-queries#binding-a-bare-value-with-param), which names types the same way.
+
+Each `member(name, storedValue)` pairs the TypeScript-visible name with the value stored in the column. Fields reference the enum with `field.namedType(Priority)`, and defaults reference a member as `Priority.members.Low`. Include the enum in the returned `enums` map so it reaches the two files.
+
+Members are stored in whatever column type you name here, `text` above. For a real PostgreSQL `enum` type, declare it with `nativeEnum` and type the field with `pg.enum`, both exported by `@prisma/orm-postgres/contract-builder`. The type is created in the database under the name you give it:
```typescript
-const Priority = enumType(
- "Priority",
- { codecId: "pg/text@1", nativeType: "text" } as const,
- member("Low", "low"),
- member("High", "high"),
-);
+import { defineContract, nativeEnum, pg } from "@prisma/orm-postgres/contract-builder";
+
+const Role = nativeEnum("Role", "user", "admin");
+
+export const contract = defineContract({}, ({ field, model }) => {
+ const Account = model("Account", { fields: { role: field.column(pg.enum(Role)) } });
+ return { models: { Account: Account.sql({ table: "account" }) } };
+});
```
-Each `member(name, storedValue)` pairs the TypeScript-visible name with the value stored in the column. Fields reference the enum with `field.namedType(Priority)`, and defaults reference a member as `Priority.members.Low`. Include the enum in the factory's returned `enums` map so it is emitted.
+`Role` does not go in the returned `enums` map. Using it on a field is enough to create the type in the database.
## Relations
-Relations are declared on the model builder with `.relations(...)` and the `rel` helpers:
+Relations are declared on the model builder with `.relations(...)` and the `rel` helpers, as the full example above shows.
+
+`rel.hasMany(Model, { by })` names the foreign key field on the other model. `rel.hasOne(Model, { by })` is the same with at most one row on the other side. `rel.belongsTo(Model, { from, to })` maps the local foreign key field to the field it points at.
+
+`rel.manyToMany(Model, { through, from, to })` goes through a join table. You declare the model for the join table yourself and pass it as `through`. Below, `PostTag` is that model, and it holds the two foreign key fields `postId` and `tagId`:
```typescript
-User.relations({
- posts: rel.hasMany(Post, { by: "userId" }),
+Post.relations({
+ tags: rel.manyToMany(Tag, { through: PostTag, from: "postId", to: "tagId" }),
});
+```
+`rel.belongsTo` on its own does not create a foreign key constraint in the database. Ask for one by chaining `.sql(...)` on the relation itself, inside the `.relations({ ... })` object, as the full example above does:
+
+```typescript
Post.relations({
- user: rel.belongsTo(User, { from: "userId", to: "id" }),
+ user: rel.belongsTo(User, { from: "userId", to: "id" }).sql({ fk: { name: "post_userId_fkey" } }),
});
```
-`rel.hasMany(Model, { by })` names the foreign key field on the other model. `rel.belongsTo(Model, { from, to })` maps the local foreign key field to the referenced field. `rel.hasOne` and `rel.manyToMany` cover the remaining shapes. A typo in a relation target is a compile error either way: the PostgreSQL builder takes model objects, and the MongoDB builder takes model names that are type-checked against the declared models.
+`fk` takes `name`, `onDelete`, and `onUpdate`. `onDelete` and `onUpdate` each take `'noAction'`, `'restrict'`, `'cascade'`, `'setNull'`, or `'setDefault'`.
+
+Prisma ORM does not check that the column types on the two sides of a relation match, so choose a field helper that produces the same column type as the key you point at.
+
+`rel.hasMany`, `rel.hasOne`, `rel.belongsTo`, and `rel.manyToMany` also accept the model name as a string. Pass the model object, as above, and a typo is a compile error. Pass a string and the typo is reported when `prisma contract emit` builds the contract.
## Storage mapping
-`.sql(...)` maps a model to its table. The object form covers the common case:
+`.sql(...)` maps a model to its table. The object form covers the common case: `User.sql({ table: "user" })`.
-```typescript
-User.relations({ ... }).sql({ table: "user" });
-```
+You can chain `.relations(...)`, `.attributes(...)`, and `.sql(...)` in any order, and you can skip any of them. A model with no relations calls `.sql({ table })` straight after `model(...)`.
-The callback form additionally exposes the model's columns and constraint builders, for foreign keys with explicit names:
+The callback form gives you the model's columns as `cols` and the constraint builders as `constraints`. Use it for indexes:
```typescript
Post.relations({ ... }).sql(({ cols, constraints }) => ({
table: "post",
- foreignKeys: [
- constraints.foreignKey(cols.userId, User.refs.id, { name: "post_userId_fkey" }),
+ indexes: [
+ constraints.index([cols.userId]),
+ constraints.index([cols.userId, cols.createdAt], { name: "post_user_created_idx" }),
],
}));
```
-`Model.refs` provides typed references to another model's fields, so `User.refs.id` is checked against the actual `User` definition.
+`constraints.index` always takes a list of columns, even when the list has one entry. Add `{ unique: true }` to make it a unique index.
-## Extension types
+`Model.refs` provides typed references to another model's fields, for the constraint builders inside `.sql(...)`. Write `constraints.foreignKey(cols.userId, User.refs.id)` and TypeScript checks `id` against the actual `User` definition.
-Declare packs in `defineContract`'s options and the factory's `type` helper exposes their constructors:
+For a primary key made of two fields, use `.attributes(...)` instead of `.sql(...)`, and build it from the field references:
```typescript
-import pgvector from "@prisma/orm-extension-pgvector/pack";
+PostTag.attributes(({ fields, constraints }) => ({
+ id: constraints.id([fields.postId, fields.tagId]),
+}));
+```
-export const contract = defineContract(
- { extensions: { pgvector } },
- ({ field, model, type }) => {
- const types = { Embedding1536: type.pgvector.Vector(1536) } as const;
- // ... use field.namedType(types.Embedding1536) in a model
- return { types, models: { /* ... */ } };
- },
-);
+That object accepts exactly two keys, `id` and `uniques`. `id` takes one constraint, and `uniques` takes a list, so `uniques: [constraints.unique([fields.postId, fields.tagId])]` makes a unique constraint across two fields. For a key made of one field, `.id()` on the field is enough, and the `field.id.*` helpers already do it.
+
+## Extension types
+
+An extension pack is an npm package that adds column types to the builder. List packs in `defineContract`'s options object, and the `type` helper exposes their constructors:
+
+```typescript title="src/prisma/contract.ts"
+import pgvector from "@prisma/orm-extension-pgvector/pack";
+import { defineContract } from "@prisma/orm-postgres/contract-builder";
+
+export const contract = defineContract({ extensions: { pgvector } }, ({ field, model, type }) => {
+ const types = { Embedding1536: type.pgvector.Vector(1536) } as const;
+ const Post = model("Post", {
+ fields: { id: field.id.uuidv4String(), embedding: field.namedType(types.Embedding1536) },
+ });
+ return { types, models: { Post: Post.sql({ table: "post" }) } };
+});
```
-The same pack must also be composed in `prisma.config.ts` (as `extensions: [pgvector]`, using the pack's `/control` export) so the CLI and runtime agree with the contract.
+The key on `type.pgvector` is the pack's own name, not the name you gave the import. The name is in the pack's documentation, and pgvector's is `pgvector`. `Embedding1536` is a name you choose, and it appears under that name in `contract.d.ts`. Return the `types` map so the name reaches the two files.
+
+Add the same pack to `prisma.config.ts`. It goes inside `ormConfig({ ... })`, beside `contract`, and the config imports the pack's `/control` export while the contract file imports its `/pack` export:
+
+```typescript title="prisma.config.ts"
+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.ts",
+ extensions: [pgvector],
+ }),
+});
+```
-## Keep the contract file pure
+## Keep the contract file free of changing values [#keep-the-contract-file-pure]
-The contract file describes structure. When you emit it, Prisma ORM canonicalizes it to JSON and hashes it, and the same source must always produce the same bytes. That works only if the file is pure data:
+The contract file describes structure. These rules keep it usable:
-- Do not read `process.env`, the current time, or random values into the contract. A contract that changes per machine or per run breaks hashing and verification.
+- Do not read `process.env`, the current time, or random values into the contract. A contract built from those values makes `npx prisma contract emit` produce a different `contract.json` on each run.
- Keep field values plain: strings, numbers, booleans, and the builder's own objects. Functions, class instances, and `Date` objects do not serialize.
-- Keep the file free of side effects. Emission evaluates it to obtain the contract object and nothing else.
+- Keep the file free of side effects. `npx prisma contract emit` loads the file with Node.js, so anything it does while loading, such as writing a file or calling a service, happens every time you run the command. The file is ordinary TypeScript, so `import` model definitions from other files as usual.
+- `npx prisma contract emit` runs the file but does not type-check it. Type errors show in your editor and when you run `tsc`.
Configuration that legitimately varies per environment, such as the database URL, belongs in `prisma.config.ts`, not in the contract.
## Parity with PSL
-TypeScript and [PSL](/orm/contract-authoring/psl-syntax) authoring emit the same canonical artifact for an equivalent schema. You can move between the modes without any downstream change. A project still declares exactly one source of truth: the file named in the config. Keep the other form out of the project so the two can never disagree.
+TypeScript and [PSL](/orm/contract-authoring/psl-syntax) authoring produce the same `contract.json` and `contract.d.ts` for an equivalent contract. You can move between the two forms without changing anything downstream. A project names exactly one contract file in its config.
+
+No command converts a `.prisma` contract into TypeScript. To move an existing project, write the TypeScript file by hand, point `contract` in `prisma.config.ts` at it, and delete the `.prisma` file so the two can never disagree.
## Prompt your coding agent
-Projects scaffolded with `create-prisma@latest` install [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. The `prisma-8` skill covers this page. Ask your agent to:
+Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. In an existing project, run `npx prisma skills sync`. The `prisma-8` skill covers TypeScript authoring. Ask your agent to:
- "Convert this contract.prisma to the TypeScript schema builder."
-- "Using the prisma-8 skill, add a unique index to the email field in our TypeScript schema."
+- "Using the prisma-8 skill, add a unique constraint to the email field in our TypeScript schema."
## Next steps
-- Emit and inspect [the artifacts](/orm/contract-authoring/the-contract-artifact) the contract produces.
+- Inspect [`contract.json` and `contract.d.ts`](/orm/contract-authoring/the-contract-artifact), the two files the contract produces.
- Apply the contract to a database with [`db init`](/cli/db-init) or plan changes with [`migration plan`](/cli/migration-plan).
diff --git a/apps/docs/content/docs/orm/core-concepts.mdx b/apps/docs/content/docs/orm/core-concepts.mdx
index 499c3724c07..09a055a4607 100644
--- a/apps/docs/content/docs/orm/core-concepts.mdx
+++ b/apps/docs/content/docs/orm/core-concepts.mdx
@@ -58,7 +58,7 @@ Every other part of the toolchain reads these artifacts, not your source file. T
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 [The emitted artifacts](/orm/contract-authoring/the-contract-artifact) for what is inside each file.
+See [contract.json and contract.d.ts](/orm/contract-authoring/the-contract-artifact) for what is inside each file.
## Hashes and the database signature
@@ -119,7 +119,7 @@ Prisma ORM's layering exists for extensibility. Our core remains small, because
## 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 [Capabilities](/orm/contract-authoring/capabilities) for more information.
+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](/orm/contract-authoring/capabilities) for more information.
## Codecs
diff --git a/apps/docs/content/docs/orm/meta.json b/apps/docs/content/docs/orm/meta.json
index b504819cfb1..aea00d7971d 100644
--- a/apps/docs/content/docs/orm/meta.json
+++ b/apps/docs/content/docs/orm/meta.json
@@ -8,6 +8,8 @@
"index",
"core-concepts",
"coming-from-prisma-orm-7",
+ "[Prisma ORM 7 to 8 (PostgreSQL)](/guides/upgrade-prisma-orm/postgresql)",
+ "[Prisma ORM 6 to 8 (MongoDB)](/guides/upgrade-prisma-orm/mongodb)",
"---Data Modeling---",
"...data-modeling",
"---Contract Authoring---",
diff --git a/apps/docs/cspell.json b/apps/docs/cspell.json
index e1eb0c6d4d7..7ec95e47683 100644
--- a/apps/docs/cspell.json
+++ b/apps/docs/cspell.json
@@ -471,6 +471,7 @@
"dbsql",
"txexecute",
"txsql",
+ "ksuid",
"ltrim",
"rtrim"
],
diff --git a/apps/docs/src/components/layout/notebook/sidebar.tsx b/apps/docs/src/components/layout/notebook/sidebar.tsx
index 30fcb9c251f..2a23385b401 100644
--- a/apps/docs/src/components/layout/notebook/sidebar.tsx
+++ b/apps/docs/src/components/layout/notebook/sidebar.tsx
@@ -243,13 +243,13 @@ export function SidebarFolderContent({
function PageTreeFolder({ item, children }: { item: PageTree.Folder; children: ReactNode }) {
const path = useTreePath();
+ const active = path.some(
+ (node) =>
+ node === item || (node.type === "folder" && node.$id !== undefined && node.$id === item.$id),
+ );
return (
-
+
{item.index ? (
{item.icon}
diff --git a/apps/docs/src/components/version-switcher.tsx b/apps/docs/src/components/version-switcher.tsx
index 264ecdb036c..578b6b6def5 100644
--- a/apps/docs/src/components/version-switcher.tsx
+++ b/apps/docs/src/components/version-switcher.tsx
@@ -1,6 +1,7 @@
"use client";
import { usePathname, useRouter } from "next/navigation";
+import { useTreeContext } from "@fumadocs/base-ui/contexts/tree";
import { ChevronDownIcon } from "lucide-react";
import {
@@ -34,6 +35,7 @@ export function VersionSwitcher({
}) {
const pathname = usePathname() as string;
const router = useRouter();
+ const { root } = useTreeContext();
const isGettingStartedVersion = isGettingStartedVersionPathname(pathname);
const isCliVersion = isCliVersionPathname(pathname);
const isGuidesVersion = isGuidesVersionPathname(pathname);
@@ -62,6 +64,14 @@ export function VersionSwitcher({
return null;
}
+ // The ORM section lists the upgrade guides, whose paths are under /guides. The
+ // sidebar then shows the ORM tree while this switcher follows the path, so hide
+ // it rather than show a Guides switcher under an ORM header.
+ const rootIsOrm = root.name === "ORM";
+ if (rootIsOrm && getOrmVersionFromPathname(pathname) === null) {
+ return null;
+ }
+
const handleVersionChange = (newVersion: Version) => {
if (newVersion === currentVersion) return;