diff --git a/apps/docs/content/docs/orm/next/contract-authoring/capabilities.mdx b/apps/docs/content/docs/orm/next/contract-authoring/capabilities.mdx
new file mode 100644
index 0000000000..9c249b4b24
--- /dev/null
+++ b/apps/docs/content/docs/orm/next/contract-authoring/capabilities.mdx
@@ -0,0 +1,97 @@
+---
+title: Capabilities
+description: Capabilities record what your database stack supports, so Prisma Next can reject unsupported features early with a clear error.
+url: /orm/next/contract-authoring/capabilities
+metaTitle: Capabilities in Prisma Next
+metaDescription: How Prisma Next 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.
+
+Capabilities are how the [data contract](/orm/next/contract-authoring/the-data-contract) records what yours supports. Prisma Next checks them before using a gated feature, so an unsupported feature fails early, with an error that names the missing capability, instead of surfacing as a database error mid-query.
+
+## Where capabilities come from
+
+You do not write capabilities yourself. When [`prisma-next contract emit`](/cli/next/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)"
+{
+ "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
+ }
+ }
+}
+```
+
+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-next.config.ts` and re-running `contract emit` changes the capability set with no further work.
+
+## What capabilities gate
+
+Capabilities are checked at two points, both before any SQL reaches the database.
+
+**When the contract is built.** A schema feature the target cannot store fails emission 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:
+
+```text
+Field "User.tags" is a scalar list, but target "sqlite" does not support
+scalar lists (the adapter does not report the "scalarList" capability).
+```
+
+**When a query is built.** Query-builder methods that depend on a capability check the contract's matrix and throw if the key is missing, naming the method and the capability:
+
+```text
+distinctOn() requires capability postgres.distinctOn
+lateralJoin() requires capability sql.lateral
+```
+
+The error fires when your code constructs the query, which makes it easy to catch in tests: a query that uses a feature your stack lacks cannot be built at all.
+
+## Example capabilities
+
+A few of the keys the built-in components declare, to give a sense of the granularity:
+
+| Capability | Gates |
+| --- | --- |
+| `sql.lateral` | Lateral joins (`lateralJoin()`); PostgreSQL declares it, SQLite does not |
+| `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 |
+| `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 is an illustration, not the full registry; each target, adapter, and pack ships its own declarations. The emitted `contract.json` of your own project 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.
+
+## Capabilities and verification
+
+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 the separate hash-and-marker mechanism described in [the contract artifact](/orm/next/contract-authoring/the-contract-artifact#the-content-hashes): [`prisma-next db verify`](/cli/next/db-verify) checks that the database matches the contract's schema and profile.
+
+Extensions are the main source of capabilities beyond the core; [Using extensions](/orm/next/extensions/using-extensions) shows the full install-to-query flow.
+
+## Prompt your coding agent
+
+Projects scaffolded with `create-prisma` install [Prisma Next skills](/ai/tools/skills) for your coding agent; the `prisma-next-contract` skill covers this page. 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."
+
+## Next steps
+
+- See where the `capabilities` block sits in [the contract artifact](/orm/next/contract-authoring/the-contract-artifact).
+- Compose extension packs, and the capabilities they bring, in the [CLI configuration](/cli/next/configuration).
diff --git a/apps/docs/content/docs/orm/next/contract-authoring/meta.json b/apps/docs/content/docs/orm/next/contract-authoring/meta.json
new file mode 100644
index 0000000000..14a42a9e60
--- /dev/null
+++ b/apps/docs/content/docs/orm/next/contract-authoring/meta.json
@@ -0,0 +1,10 @@
+{
+ "title": "Contract authoring",
+ "pages": [
+ "the-data-contract",
+ "psl-syntax",
+ "typescript-schema-builder",
+ "the-contract-artifact",
+ "capabilities"
+ ]
+}
diff --git a/apps/docs/content/docs/orm/next/contract-authoring/psl-syntax.mdx b/apps/docs/content/docs/orm/next/contract-authoring/psl-syntax.mdx
new file mode 100644
index 0000000000..f31667773b
--- /dev/null
+++ b/apps/docs/content/docs/orm/next/contract-authoring/psl-syntax.mdx
@@ -0,0 +1,346 @@
+---
+title: Author in PSL
+description: Write the Prisma Next contract as a Prisma schema file, using the schema language you already know plus the Prisma Next additions.
+url: /orm/next/contract-authoring/psl-syntax
+metaTitle: Author the Prisma Next contract in PSL
+metaDescription: Learn how to write a Prisma Next 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 Next [data contract](/orm/next/contract-authoring/the-data-contract). You write a Prisma schema file, usually `prisma/contract.prisma`, and [`prisma-next contract emit`](/cli/next/contract-emit) turns it into `contract.json` and `contract.d.ts`.
+
+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 Next additions in detail: named types, typed enums, value objects, base models with variants, and extension types.
+
+## A complete contract
+
+
+
+
+
+```prisma title="prisma/contract.prisma"
+// use prisma-next
+
+types {
+ Uuid = String @db.Uuid
+}
+
+type Address {
+ street String
+ city String
+ zip String?
+ country String
+}
+
+enum Priority {
+ @@type("pg/text@1")
+ Low = "low"
+ High = "high"
+ Urgent = "urgent"
+}
+
+model User {
+ id Uuid @id @default(uuid())
+ email String
+ createdAt DateTime @default(now())
+ address Address?
+ posts Post[]
+
+ @@map("user")
+}
+
+model Post {
+ id Uuid @id @default(uuid())
+ title String
+ userId Uuid
+ priority Priority @default(Low)
+ createdAt DateTime @default(now())
+
+ user User @relation(fields: [userId], references: [id])
+
+ @@map("post")
+}
+```
+
+
+
+
+
+```prisma title="prisma/contract.prisma"
+// use prisma-next
+
+type Address {
+ street String
+ city String
+ zip String?
+ country String
+}
+
+enum UserRole {
+ @@type("mongo/string@1")
+ Admin = "admin"
+ Author = "author"
+ Reader = "reader"
+}
+
+model User {
+ id ObjectId @id @map("_id")
+ name String
+ email String
+ bio String?
+ role UserRole
+ address Address?
+ posts Post[]
+
+ @@map("users")
+}
+
+model Post {
+ id ObjectId @id @map("_id")
+ title String
+ content String
+ authorId ObjectId
+ createdAt DateTime
+
+ author User @relation(fields: [authorId], references: [id])
+
+ @@index([authorId])
+ @@map("posts")
+}
+```
+
+
+
+
+
+Run `npx prisma-next contract emit` after any change to refresh the artifacts.
+
+## Point the config at the schema
+
+The config's `contract` path names the source of truth. A `.prisma` extension selects PSL authoring:
+
+
+
+
+
+```typescript title="prisma-next.config.ts"
+import { defineConfig } from "@prisma-next/postgres/config";
+
+export default defineConfig({
+ contract: "./prisma/contract.prisma",
+});
+```
+
+
+
+
+
+```typescript title="prisma-next.config.ts"
+import { defineConfig } from "@prisma-next/mongo/config";
+
+export default defineConfig({
+ contract: "./prisma/contract.prisma",
+});
+```
+
+
+
+
+
+Scaffolded projects (`npx create-prisma@next`) already contain this wiring and a starter schema.
+
+## Models and fields
+
+Models declare fields with a type, an optional `?` marker, and attributes:
+
+- `@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 Next before each write instead, so they work the same on every database and 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.
+
+How IDs map differs by database:
+
+
+
+
+
+```prisma
+model User {
+ id Uuid @id @default(uuid())
+}
+```
+
+The primary key is an ordinary column; pick its type and default yourself.
+
+
+
+
+
+```prisma
+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.
+
+
+
+
+
+## Named types
+
+The `types` block declares reusable type aliases. An alias binds a base type and its storage details under one name:
+
+```prisma
+types {
+ Uuid = String @db.Uuid
+}
+```
+
+Fields then use `Uuid` like any built-in type. The alias keeps the storage decision (`uuid` columns rather than `text`) in one place.
+
+## Enums
+
+An enum in Prisma Next declares its storage codec with `@@type` and, optionally, the stored value for each member:
+
+```prisma
+enum Priority {
+ @@type("pg/text@1")
+ Low = "low"
+ High = "high"
+ Urgent = "urgent"
+}
+```
+
+`@@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, the emitted contract enforces the allowed values with a `CHECK` constraint on each column that uses the enum.
+
+## 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.
+
+```prisma
+type Address {
+ street String
+ city String
+ zip String?
+ country String
+}
+
+model User {
+ id Uuid @id @default(uuid())
+ address Address?
+}
+```
+
+Storage follows the database's nature: 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/next/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:
+
+```prisma
+model Post {
+ userId Uuid
+ user User @relation(fields: [userId], references: [id])
+}
+
+model User {
+ posts Post[]
+}
+```
+
+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:
+
+```prisma
+model Post {
+ tags Tag[]
+}
+
+model Tag {
+ posts Post[]
+}
+
+model PostTag {
+ postId Uuid
+ tagId Uuid
+
+ post Post @relation(fields: [postId], references: [id])
+ tag Tag @relation(fields: [tagId], references: [id])
+
+ @@id([postId, tagId])
+ @@map("post_tag")
+}
+```
+
+The emitted contract records the relation as `N:M` with the join table's columns, so queries can traverse `post.tags` directly.
+
+## Base models and variants
+
+A base model declares a discriminator field, and each variant names its base and its discriminator value:
+
+```prisma
+model Task {
+ id Uuid @id @default(uuid())
+ title String
+ type String
+
+ @@discriminator(type)
+ @@map("task")
+}
+
+model Bug {
+ severity String
+ stepsToRepro String?
+
+ @@base(Task, "bug")
+ @@map("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/next/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.
+
+## Extension types
+
+Extension packs contribute types through constructor expressions in the `types` block. Compose the pack in the config, then use its types:
+
+```typescript title="prisma-next.config.ts"
+import pgvector from "@prisma-next/extension-pgvector/control";
+import { defineConfig } from "@prisma-next/postgres/config";
+
+export default defineConfig({
+ contract: "./prisma/contract.prisma",
+ extensions: [pgvector],
+});
+```
+
+```prisma title="prisma/contract.prisma"
+types {
+ Embedding1536 = pgvector.Vector(1536)
+}
+
+model Post {
+ id Uuid @id @default(uuid())
+ embedding Embedding1536?
+}
+```
+
+Emission validates extension types against the composed packs: a type from a pack that is not listed in the config fails the emit with a diagnostic. Re-run `contract emit` after changing the extension list.
+
+## Starting from an existing database
+
+If the database already exists, don't write the schema by hand. [`prisma-next contract infer`](/cli/next/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/next/data-modeling/relational-databases) and [MongoDB data modeling](/orm/next/data-modeling/mongodb).
+
+## Prompt your coding agent
+
+Projects scaffolded with `create-prisma` install [Prisma Next skills](/ai/tools/skills) for your coding agent; the `prisma-next-contract` skill covers this page. Ask your agent to:
+
+- "Using the prisma-next-contract 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."
+- "Give the Post model a composite unique constraint on userId and title."
+
+## Next steps
+
+- Emit and inspect [the artifacts](/orm/next/contract-authoring/the-contract-artifact) the schema produces.
+- Prefer defining models in code? See [authoring in TypeScript](/orm/next/contract-authoring/typescript-schema-builder).
+- Apply the contract to a database with [`prisma-next db init`](/cli/next/db-init) or plan changes with [`prisma-next migration plan`](/cli/next/migration-plan).
diff --git a/apps/docs/content/docs/orm/next/contract-authoring/the-contract-artifact.mdx b/apps/docs/content/docs/orm/next/contract-authoring/the-contract-artifact.mdx
new file mode 100644
index 0000000000..e5ff97ee21
--- /dev/null
+++ b/apps/docs/content/docs/orm/next/contract-authoring/the-contract-artifact.mdx
@@ -0,0 +1,165 @@
+---
+title: The emitted artifacts
+description: contract.json and contract.d.ts are the deterministic artifacts every other part of Prisma Next consumes. Here is what is inside them.
+url: /orm/next/contract-authoring/the-contract-artifact
+metaTitle: The Prisma Next 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.
+---
+
+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.
+
+[`prisma-next contract emit`](/cli/next/contract-emit) writes both next to the source by default:
+
+| File | Contents | Consumed 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.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/next/contract-authoring/psl-syntax) or [TypeScript](/orm/next/contract-authoring/typescript-schema-builder) source and re-run `contract emit`. The files carry a generated-file notice that says exactly this.
+
+## Deterministic emission
+
+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.
+
+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/next/contract-authoring/typescript-schema-builder#keep-the-contract-file-pure) lists the concrete rules.
+
+## The content hashes
+
+Emission computes hashes over distinct parts of the contract. Each answers a different question:
+
+| Hash | Covers | Changes when |
+| --- | --- | --- |
+| `storageHash` | Models, fields, relations, and the full storage layout: tables, columns, keys, indexes | Any schema change |
+| `executionHash` | Defaults Prisma Next 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 |
+
+The hashes are how Prisma Next connects the contract to a live database. [`prisma-next db sign`](/cli/next/db-sign) checks that the database satisfies the contract and records the hashes in a marker inside the database. From then on, [`prisma-next db verify`](/cli/next/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.
+
+## 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`).
+
+An abridged emit of a `User`/`Post` schema:
+
+```json title="prisma/contract.json (abridged)"
+{
+ "schemaVersion": "1",
+ "targetFamily": "sql",
+ "target": "postgres",
+ "profileHash": "sha256:…",
+ "roots": {
+ "user": { "namespace": "public", "model": "User" },
+ "post": { "namespace": "public", "model": "Post" }
+ },
+ "domain": {
+ "namespaces": {
+ "public": {
+ "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"] }
+ }
+ },
+ "storage": {
+ "namespaceId": "public",
+ "table": "user",
+ "fields": { "id": { "column": "id" }, "email": { "column": "email" } }
+ }
+ }
+ }
+ }
+ }
+ },
+ "storage": {
+ "storageHash": "sha256:…",
+ "namespaces": {
+ "public": {
+ "entries": {
+ "table": {
+ "user": {
+ "columns": {
+ "id": { "nativeType": "uuid", "codecId": "pg/uuid@1", "nullable": false },
+ "email": { "nativeType": "text", "codecId": "pg/text@1", "nullable": false }
+ },
+ "primaryKey": { "columns": ["id"] }
+ }
+ }
+ }
+ }
+ }
+ },
+ "execution": { "executionHash": "sha256:…" },
+ "capabilities": { "postgres": { "returning": true } },
+ "extensionPacks": {}
+}
+```
+
+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. `db.user` exists because `roots.user` points at `User`.
+- **`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 Next applies before writes, such as UUID generation, kept out of the database's DDL.
+- **`capabilities`** and **`extensionPacks`**: 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/next/contract-authoring/capabilities).
+
+## 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:
+
+```typescript title="prisma/contract.d.ts (excerpt)"
+export type StorageHash =
+ StorageHashBase<"sha256:9f49f8f9e51a9cc016f1ec2098ebae9406521a3cc2cf00207adc795078333d8b">;
+export type ProfileHash =
+ ProfileHashBase<"sha256: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"];
+};
+```
+
+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.
+
+## 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.
+
+```typescript title="src/prisma/db.ts"
+import postgres from "@prisma-next/postgres/runtime";
+import type { Contract } from "./contract.d";
+import contractJson from "./contract.json" with { type: "json" };
+
+export const db = postgres({
+ contractJson,
+});
+```
+
+At startup, the client carries the contract's hashes and verifies them against the database marker before executing queries.
+
+## 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.
+
+## Prompt your coding agent
+
+Projects scaffolded with `create-prisma` install [Prisma Next skills](/ai/tools/skills) for your coding agent; the `prisma-next-contract` skill covers this page. 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."
+
+## Next steps
+
+- Learn how the contract's [capabilities](/orm/next/contract-authoring/capabilities) gate database features.
+- Apply the contract to a fresh database with [`prisma-next db init`](/cli/next/db-init).
+- Check a live database against the contract with [`prisma-next db verify`](/cli/next/db-verify).
+- Plan schema changes between contract versions with [`prisma-next migration plan`](/cli/next/migration-plan).
diff --git a/apps/docs/content/docs/orm/next/contract-authoring/the-data-contract.mdx b/apps/docs/content/docs/orm/next/contract-authoring/the-data-contract.mdx
new file mode 100644
index 0000000000..289c914fa7
--- /dev/null
+++ b/apps/docs/content/docs/orm/next/contract-authoring/the-data-contract.mdx
@@ -0,0 +1,136 @@
+---
+title: The data contract
+description: The data contract is the single description of your data model and its storage layout. Everything in Prisma Next is typed, planned, and verified against it.
+url: /orm/next/contract-authoring/the-data-contract
+metaTitle: The Prisma Next data contract
+metaDescription: What the Prisma Next data contract is, how PSL and TypeScript authoring produce the same artifacts, and how the contract drives typing, migrations, and verification.
+---
+
+Every Prisma Next project has one description of its data: the models, their fields, and how they map to database tables. That description is the data contract.
+
+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"
+model User {
+ id Int @id @default(autoincrement())
+ email String @unique
+ posts Post[]
+}
+
+model Post {
+ id Int @id @default(autoincrement())
+ title String
+ userId Int
+
+ user User @relation(fields: [userId], references: [id])
+}
+```
+
+Prisma Next compiles this into a machine-readable artifact, and everything else checks itself 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.
+
+Think of it as a `package-lock.json` for your data: an exact, versioned record of what your application expects from its database.
+
+:::note[Contract vs. schema]
+
+In Prisma Next, 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 Next checks that the database's schema satisfies it.
+
+:::
+
+## 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.
+
+Prisma Next 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 carries identity. Emission computes hashes over the contract's content, and [`prisma-next db sign`](/cli/next/db-sign) records them in a small marker inside the database. Before executing queries, Prisma Next compares the contract it was built against with the marker in the database it is talking to. A mismatch, such as a deploy against an unmigrated database, fails verification instead of failing at query time.
+
+## How it works
+
+A Prisma Next project declares one contract source in `prisma-next.config.ts`:
+
+```typescript title="prisma-next.config.ts"
+import { defineConfig } from "@prisma-next/postgres/config";
+
+export default defineConfig({
+ contract: "./prisma/contract.prisma",
+});
+```
+
+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.
+
+Emitting turns the source into the artifacts:
+
+```bash
+npx prisma-next contract emit
+```
+
+This writes `contract.json` and `contract.d.ts` next to the source. From there, the rest of the toolchain takes over:
+
+- 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 [`prisma-next db verify`](/cli/next/db-verify) checks a live database against the current contract.
+
+## Two authoring modes, one artifact
+
+**[PSL](/orm/next/contract-authoring/psl-syntax)** is the preferred authoring surface: a compact language purpose-built for describing data. It is what `create-prisma` scaffolds, what [`prisma-next contract infer`](/cli/next/contract-infer) writes when you start from an existing database, and what the examples throughout these docs use.
+
+For the cases PSL does not cover, defining models with a **[TypeScript builder](/orm/next/contract-authoring/typescript-schema-builder)** exists as an escape hatch: reach for it when model definitions must be composed, generated, or shared as ordinary TypeScript modules.
+
+Both modes are front ends to the same contract. For an equivalent schema they emit the same `contract.json`, so nothing downstream, including migrations, verification, and the query APIs, cares which one you write, and 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.
+
+## 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
+
+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.
+
+## Prompt your coding agent
+
+Projects scaffolded with `create-prisma` install [Prisma Next skills](/ai/tools/skills) for your coding agent; the `prisma-next-contract` skill covers this page. Ask your agent to:
+
+- "Using the prisma-next-contract skill, explain what our contract.json currently declares."
+- "Add an Invoice model to the contract and emit it."
+- "Check whether our database still satisfies the contract."
+
+## Next steps
+
+- [Model your data](/orm/next/data-modeling) before writing the contract: models, keys, and relations.
+- [Query against the contract](/orm/next/fundamentals/reading-data): every result is typed by what you authored here.
+
+
+ }
+ >
+ Write the contract as a Prisma schema file.
+
+ }
+ >
+ Define the same models with the typed contract builder.
+
+ }
+ >
+ What is inside contract.json and contract.d.ts, and how the hashes work.
+
+ }
+ >
+ How Prisma Next checks that your database supports what the contract
+ needs.
+
+
diff --git a/apps/docs/content/docs/orm/next/contract-authoring/typescript-schema-builder.mdx b/apps/docs/content/docs/orm/next/contract-authoring/typescript-schema-builder.mdx
new file mode 100644
index 0000000000..2c6434c85a
--- /dev/null
+++ b/apps/docs/content/docs/orm/next/contract-authoring/typescript-schema-builder.mdx
@@ -0,0 +1,300 @@
+---
+title: Author in TypeScript
+description: Define the Prisma Next contract with a typed builder API instead of a schema file. Same models, same artifacts, no separate language.
+url: /orm/next/contract-authoring/typescript-schema-builder
+metaTitle: Author the Prisma Next contract in TypeScript
+metaDescription: Learn how to define a Prisma Next contract in TypeScript with defineContract, including fields, relations, enums, storage mapping, and extension types.
+---
+
+TypeScript authoring defines the Prisma Next [data contract](/orm/next/contract-authoring/the-data-contract) in code. Instead of a `.prisma` file, you write `prisma/contract.ts` with the `defineContract` builder, and [`prisma-next contract emit`](/cli/next/contract-emit) produces exactly the same `contract.json` and `contract.d.ts` a PSL schema would.
+
+## When to choose TypeScript over PSL
+
+[PSL](/orm/next/contract-authoring/psl-syntax) is the preferred authoring surface, and since both modes emit identical artifacts, you give up nothing by staying with it. The TypeScript builder is an escape hatch 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)
+
+If neither applies, write PSL: it is more compact, and it is what `create-prisma` scaffolds and [`prisma-next contract infer`](/cli/next/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:
+
+
+
+
+
+```typescript title="prisma-next.config.ts"
+import { defineConfig } from "@prisma-next/postgres/config";
+
+export default defineConfig({
+ contract: "./prisma/contract.ts",
+});
+```
+
+
+
+
+
+```typescript title="prisma-next.config.ts"
+import { defineConfig } from "@prisma-next/mongo/config";
+
+export default defineConfig({
+ contract: "./prisma/contract.ts",
+});
+```
+
+
+
+
+
+## A complete contract
+
+The builder comes from your database's target package: `@prisma-next/postgres/contract-builder` for PostgreSQL, `@prisma-next/mongo/contract-builder` for MongoDB.
+
+
+
+
+
+```typescript title="prisma/contract.ts"
+import pgvector from "@prisma-next/extension-pgvector/pack";
+import { defineContract, enumType, member, rel } from "@prisma-next/postgres/contract-builder";
+
+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(
+ {
+ extensionPacks: { 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",
+ }),
+ ],
+ })),
+ },
+ };
+ },
+);
+```
+
+
+
+
+
+```typescript title="prisma/contract.ts"
+import { defineContract, field, model, rel } from "@prisma-next/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" }),
+ },
+});
+
+const Post = model("Post", {
+ collection: "posts",
+ fields: {
+ _id: field.objectId(),
+ authorId: field.objectId(),
+ title: field.string(),
+ publishedAt: field.date().optional(),
+ },
+ relations: {
+ author: rel.belongsTo(User, { from: "authorId", to: User.ref("_id") }),
+ },
+});
+
+export const contract = defineContract({
+ models: {
+ User,
+ Post,
+ },
+});
+```
+
+
+
+
+
+Run `npx prisma-next contract emit` after any change to refresh the artifacts.
+
+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`, scalar helpers are `field.string()`, `field.int32()`, `field.double()`, `field.bool()`, and `field.date()`, and the collection is an inline `collection` option on the model rather than a chained call.
+
+## How `defineContract` works
+
+`defineContract` takes two arguments: an options object and a factory function.
+
+The options object declares what the contract is built from, most importantly `extensionPacks`. The target and database family are already bound by the import: `@prisma-next/postgres/contract-builder` produces PostgreSQL contracts, so you never name them yourself.
+
+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 (which is why `type.pgvector` exists in the example, and only when `pgvector` is in `extensionPacks`). The factory returns the contract's content: `models`, plus optional `enums` and `types`.
+
+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.
+
+## Fields
+
+`field` provides typed constructors for common field shapes:
+
+- `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
+
+The exact helper set comes from the target and the composed extension packs. 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.
+
+## Enums
+
+`enumType` declares an enum with an explicit storage codec and members:
+
+```typescript
+const Priority = enumType(
+ "Priority",
+ { codecId: "pg/text@1", nativeType: "text" } as const,
+ member("Low", "low"),
+ member("High", "high"),
+);
+```
+
+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.
+
+## Relations
+
+Relations are declared on the model builder with `.relations(...)` and the `rel` helpers:
+
+```typescript
+User.relations({
+ posts: rel.hasMany(Post, { by: "userId" }),
+});
+
+Post.relations({
+ user: rel.belongsTo(User, { from: "userId", to: "id" }),
+});
+```
+
+`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. Because the arguments are model objects, not strings, a typo in a relation target is a compile error.
+
+## Storage mapping
+
+`.sql(...)` maps a model to its table. The object form covers the common case:
+
+```typescript
+User.relations({ ... }).sql({ table: "user" });
+```
+
+The callback form additionally exposes the model's columns and constraint builders, for foreign keys with explicit names:
+
+```typescript
+Post.relations({ ... }).sql(({ cols, constraints }) => ({
+ table: "post",
+ foreignKeys: [
+ constraints.foreignKey(cols.userId, User.refs.id, { name: "post_userId_fkey" }),
+ ],
+}));
+```
+
+`Model.refs` provides typed references to another model's fields, so `User.refs.id` is checked against the actual `User` definition.
+
+## Extension types
+
+Declare packs in `defineContract`'s options and the factory's `type` helper exposes their constructors:
+
+```typescript
+import pgvector from "@prisma-next/extension-pgvector/pack";
+
+export const contract = defineContract(
+ { extensionPacks: { pgvector } },
+ ({ field, model, type }) => {
+ const types = { Embedding1536: type.pgvector.Vector(1536) } as const;
+ // ... use field.namedType(types.Embedding1536) in a model
+ return { types, models: { /* ... */ } };
+ },
+);
+```
+
+The same pack must also be composed in `prisma-next.config.ts` (as `extensions: [pgvector]`, using the pack's `/control` export) so the CLI and runtime agree with the contract.
+
+## Keep the contract file pure
+
+The contract file describes structure; emission 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:
+
+- 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.
+- 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.
+
+Configuration that legitimately varies per environment, such as the database URL, belongs in `prisma-next.config.ts`, not in the contract.
+
+## Parity with PSL
+
+TypeScript and [PSL](/orm/next/contract-authoring/psl-syntax) authoring emit the same canonical artifact for an equivalent schema. You can move between the modes without any downstream change, but a project 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.
+
+## Prompt your coding agent
+
+Projects scaffolded with `create-prisma` install [Prisma Next skills](/ai/tools/skills) for your coding agent; the `prisma-next-contract` skill covers this page. Ask your agent to:
+
+- "Convert this contract.prisma to the TypeScript schema builder."
+- "Using the prisma-next-contract skill, add a unique index to the email field in our TypeScript schema."
+
+## Next steps
+
+- Emit and inspect [the artifacts](/orm/next/contract-authoring/the-contract-artifact) the contract produces.
+- Apply the contract to a database with [`prisma-next db init`](/cli/next/db-init) or plan changes with [`prisma-next migration plan`](/cli/next/migration-plan).
diff --git a/apps/docs/content/docs/orm/next/index.mdx b/apps/docs/content/docs/orm/next/index.mdx
index 2dbafc33b2..4b94e89e42 100644
--- a/apps/docs/content/docs/orm/next/index.mdx
+++ b/apps/docs/content/docs/orm/next/index.mdx
@@ -92,4 +92,12 @@ Prisma Next is in active development, and full conceptual and reference document
Create a Prisma Next app, initialize the database, seed data, and run your
first query.
+ }
+ >
+ Learn how your schema becomes a verifiable contract, authored in PSL or
+ TypeScript.
+
diff --git a/apps/docs/content/docs/orm/next/meta.json b/apps/docs/content/docs/orm/next/meta.json
index 83bff2fb31..f35013acbb 100644
--- a/apps/docs/content/docs/orm/next/meta.json
+++ b/apps/docs/content/docs/orm/next/meta.json
@@ -7,13 +7,12 @@
"index",
"---Data Modeling---",
"...data-modeling",
-
+ "---Contract Authoring---",
+ "...contract-authoring",
"---Fundamentals---",
"...fundamentals",
-
"---Middleware---",
"...middleware",
-
"---Extensions---",
"...extensions"
]
diff --git a/apps/docs/cspell.json b/apps/docs/cspell.json
index cce0b6b964..53f515f88c 100644
--- a/apps/docs/cspell.json
+++ b/apps/docs/cspell.json
@@ -4,14 +4,11 @@
"language": "en",
"words": [
"ABAC",
- "Activeusers",
"accountid",
+ "Activeusers",
"Aiven",
"amcheck",
"amet",
- "lanczos",
- "lanczos3",
- "EXIF",
"Amplication",
"Ania",
"anotherproduct",
@@ -22,11 +19,11 @@
"Atrue",
"authjs",
"autoinc",
- "autosuspend",
"autoincrement",
"AUTOINCREMENT",
"autoincrementing",
"AUTOINSTALL",
+ "autosuspend",
"baselining",
"Baselining",
"behaviour",
@@ -34,10 +31,6 @@
"betterauth",
"bigserial",
"BIGSERIAL",
- "btree",
- "btrees",
- "Btree",
- "Btrees",
"bindefault",
"biograpy",
"blobshape",
@@ -45,6 +38,10 @@
"Bobo",
"Bpchar",
"bridg",
+ "btree",
+ "Btree",
+ "btrees",
+ "Btrees",
"Buildpacks",
"bunx",
"Burk",
@@ -82,27 +79,28 @@
"dbml",
"DBML",
"dbname",
+ "diffable",
"distancesphere",
- "dogfooding",
"Distroless",
"distros",
"Dmmf",
"Dockerfiles",
+ "dogfooding",
"dotenvx",
- "dwithin",
"Dreamies",
- "geospatial",
+ "dwithin",
"earthdistance",
"ecommerce",
"Ecommerce",
"edouardb",
"elysia",
"Elysia",
+ "emailverified",
"Emelie",
"Enya",
"epsg",
- "emailverified",
"everytime",
+ "EXIF",
"extralight",
"favorited",
"Felinecitas",
@@ -120,6 +118,7 @@
"geofencing",
"Geofencing",
"geometrycollection",
+ "geospatial",
"glassmorphism",
"Glassmorphism",
"gofmt",
@@ -155,6 +154,8 @@
"Kwame",
"kysely",
"Kysely",
+ "lanczos",
+ "lanczos3",
"lastname",
"lastproduct",
"lexmata",
@@ -205,8 +206,8 @@
"mysqldump",
"napi",
"NDEKTSV",
- "neondb",
"neondatabase",
+ "neondb",
"Neward",
"nextauth",
"nextval",
@@ -234,8 +235,8 @@
"packagemanager",
"Pacman",
"pageinspect",
- "pegasusheavy",
"paradedb",
+ "pegasusheavy",
"permitio",
"pgbouncer",
"pgcat",
@@ -293,6 +294,7 @@
"rootca",
"RRFFQ",
"RSPCA",
+ "s3cret",
"Sabelle",
"safeql",
"Saqui",
@@ -318,7 +320,6 @@
"sqlcommenter",
"srid",
"SRID",
- "s3cret",
"sslaccept",
"sslcert",
"sslidentity",
@@ -379,13 +380,15 @@
"unikernel",
"unikernels",
"unindexed",
- "untick",
"unixepoch",
+ "unmigrated",
+ "untick",
"uploadthing",
"UPLOADTHING",
"upserting",
"Upserting",
"upserts",
+ "uuidv",
"Valkey",
"VARBINARY",
"varbit",
@@ -415,6 +418,8 @@
"pattern": "/videoId=\"[A-Za-z0-9_-]{6,}\"/g"
}
],
- "ignoreRegExpList": ["mdxVideoIdAttribute"],
+ "ignoreRegExpList": [
+ "mdxVideoIdAttribute"
+ ],
"ignorePaths": []
}
diff --git a/apps/docs/next.config.mjs b/apps/docs/next.config.mjs
index 3fae170b2e..4e30943e82 100644
--- a/apps/docs/next.config.mjs
+++ b/apps/docs/next.config.mjs
@@ -315,6 +315,13 @@ const config = {
// { source: "/orm/prisma-client/queries/transactions", destination: "/orm/next/fundamentals/transactions", permanent: false },
// { source: "/orm/prisma-client/using-raw-sql", destination: "/orm/next/fundamentals/advanced-queries", permanent: false },
//
+ // DR-8680 Contract authoring:
+ // { source: "/orm/prisma-schema", destination: "/orm/next/contract-authoring/the-data-contract", permanent: false },
+ // { source: "/orm/prisma-schema/overview", destination: "/orm/next/contract-authoring/psl-syntax", permanent: false },
+ // { source: "/orm/prisma-schema/overview/data-sources", destination: "/orm/next/contract-authoring/psl-syntax", permanent: false },
+ // { source: "/orm/prisma-schema/overview/location", destination: "/orm/next/contract-authoring/psl-syntax", permanent: false },
+ // { source: "/orm/prisma-client/type-safety", destination: "/orm/next/contract-authoring/the-data-contract", permanent: false },
+ //
// No Prisma Next equivalent yet (stay on the Prisma 7 tree, flag to the
// SEO owner at cutover): /orm/prisma-client/queries/full-text-search,
// /orm/prisma-client/queries/advanced/query-optimization-performance,