| name | prisma-next-contract |
| description | Edit the Prisma Next data contract — add models, fields, relations, indexes, enums, value objects (composite types), type aliases, namespaces (Postgres schemas), cross-contract foreign keys (cross-space FK), polymorphic types (`@@discriminator` / `@@base`), use extension namespaces (`pgvector.Vector(...)`, `cipherstash.EncryptedString(...)`), wire `prisma-next.config.ts` with `defineConfig` from the `@prisma-next/<target>/config` façade, and run `prisma-next contract emit`. Use for schema, models, fields, attributes, soft delete, paranoid, scopes, validations, callbacks, prisma schema, PSL, contract.prisma, contract.ts, contract.json, contract.d.ts, `@prisma-next/postgres/config`, `@prisma-next/postgres/contract-builder`, `@prisma-next/postgres/control`, `@prisma-next/mongo/config`, `@prisma-next/mongo/contract-builder`, `extensions:`, pgvector, cipherstash, postgis, paradedb, supabase, `@prisma-next/extension-supabase`, `@@control`, control policy, managed, tolerated, external, observed. |
Prisma Next — Contract Authoring
Edit your data contract. Prisma handles the rest.
The data contract is the single source of truth for your data layer. You edit a contract source — contract.prisma (PSL, the canonical surface) or contract.ts (TypeScript builder) — and the framework derives types, migrations, and runtime configuration from it. The three-step user model:
- You edit your data contract.
- The system plans the migrations for you. (
prisma-next-migrations)
- If you need data migrations, you edit
migration.ts and execute it. (prisma-next-migrations)
Behind step 1 the agent runs prisma-next contract emit after every contract edit (or installs the Vite plugin so the bundler runs it on save — see prisma-next-build). Emit reads the contract source through the provider the façade picks based on the file extension of contract: in prisma-next.config.ts, then writes two artefacts colocated with the source:
contract.json — the canonical, content-hashed Contract IR. Read by the planner, the runtime, and db verify.
contract.d.ts — the precise TypeScript types the runtime + lanes propagate when you import Contract from it.
Both files are emitted artefacts. Edit the source; never the JSON or .d.ts.
When to Use
- User wants to add, change, or remove a model / field / relation.
- User wants to add an index, unique constraint, enum, or value object (composite type).
- User wants to add a namespace block (Postgres schema) or a cross-contract foreign key.
- User wants to set
@@control on a model or configure defaultControlPolicy.
- User wants to use a custom type from an extension (
pgvector.Vector(length: 1536), cipherstash.EncryptedString({...})).
- User wants to install or configure an extension via
extensions: [...] in prisma-next.config.ts, including @prisma-next/extension-supabase.
- User is migrating between authoring sources (PSL ↔ TypeScript builder).
- User received
PN-CLI-4002, PN-CLI-4003, or PN-CLI-4011 from contract emit.
- User mentions: schema, fields, models, attributes, prisma schema, PSL, contract.prisma, contract.ts, contract.json, contract.d.ts, contract emit, façade imports,
@prisma-next/postgres/config, @prisma-next/postgres/contract-builder, extensions, pgvector, cipherstash, postgis, paradedb, supabase, namespaces, cross-space FK, @@control, enums, value objects, validations, callbacks, soft delete, paranoid, scopes. (The last cluster routes to What Prisma Next doesn't do yet below.)
When Not to Use
- User wants to apply a contract change to the DB →
prisma-next-migrations.
- User wants to write a query against the contract →
prisma-next-queries.
- User wants to wire
db.ts (runtime entry point, middleware, env config) → prisma-next-runtime.
- User wants the Vite / bundler integration →
prisma-next-build.
- User wants to set up Prisma Next for the first time →
prisma-next-quickstart.
- User wants a deeper read of a single structured error envelope →
prisma-next-debug.
- User wants to file a missing-feature request →
prisma-next-feedback.
Key Concepts
-
The @prisma-next/<target> façade is the only surface user-authored code imports from. For a Postgres app: @prisma-next/postgres/config, @prisma-next/postgres/contract-builder, @prisma-next/postgres/control, @prisma-next/postgres/runtime. Mongo has the same layout (@prisma-next/mongo/config, @prisma-next/mongo/contract-builder, @prisma-next/mongo/runtime). Each extension publishes its own façade — @prisma-next/extension-pgvector/control, @prisma-next/extension-postgis/control, @prisma-next/extension-paradedb/control. Never reach into @prisma-next/cli/*, @prisma-next/family-*, @prisma-next/target-*, @prisma-next/adapter-*, @prisma-next/driver-*, or @prisma-next/sql-contract-* from user code. The façade bakes the family / target / adapter / driver wiring in. See Common Pitfalls #4.
-
Contract source. A file the framework reads and lowers to the canonical Contract IR. Two flavours, both first-class:
contract.prisma (PSL) — schema-flavoured DSL. Canonical for typical apps and brownfield Prisma users. Wired by contract: './<path>/contract.prisma' — the defineConfig façade detects the .prisma extension and routes through the PSL provider.
contract.ts (TypeScript builder) — programmatic authoring with defineContract({...}, ({ field, model, rel, type }) => ({...})) from @prisma-next/postgres/contract-builder (or @prisma-next/mongo/contract-builder). Wired by contract: './<path>/contract.ts' — the façade detects the .ts extension and routes through the TS provider. Use when you need programmatic composition (per-tenant variants, generated fields) or constructs PSL doesn't yet express (e.g. registering a parameterised extension type — see pgvector's contract).
-
prisma-next.config.ts. Wires the contract source, the database connection, the migrations directory, and any installed extensions. Use defineConfig({...}) from @prisma-next/postgres/config (or @prisma-next/mongo/config). The four fields the façade accepts: contract (path string — .prisma or .ts), db ({ connection?: string }), extensions (array of control descriptors), migrations ({ dir?: string }). The output path for contract.json is auto-derived from contract (e.g. ./src/prisma/contract.prisma → ./src/prisma/contract.json).
-
Emit pipeline. prisma-next contract emit --config <path>? reads prisma-next.config.ts, calls the provider the façade picked, validates the resulting Contract, then atomically writes contract.json + contract.d.ts colocated with the source.
-
Extension namespaces. Extensions contribute namespaced constructors (pgvector.Vector(length: 1536), cipherstash.EncryptedString({equality: true})) and helper presets. Install them by adding the descriptor to two places — both fields are named extensions, but the two surfaces consume two different descriptor types and shapes:
- In the config (façade and core):
extensions: [pgvector] — array of control descriptors imported from @prisma-next/extension-<name>/control.
- In the TS builder's
defineContract (only when authoring contract.ts): extensions: { pgvector } — record of pack descriptors imported from @prisma-next/extension-<name>/pack.
-
Contract space. Every package that emits a contract owns its own contract space — a prisma-next.config.ts at package root, a contract source, the colocated emitted artefacts, and a migrations/ directory. There are two intentional on-disk layouts, picked by whether the contract space is the consuming application or a contract-space package (an extension, an internal aggregate-root package, etc.):
- Application layout (what you use when building an app).
prisma-next.config.ts at repo root; src/prisma/contract.{prisma,ts}; src/prisma/contract.{json,d.ts} colocated; src/prisma/db.ts colocated; migrations under migrations/app/<timestamp>_<slug>/. The app/ segment is the consuming application's space-id; extension space-ids land in sibling migrations/<extension-space-id>/ directories that the extension packages manage. This is what examples/prisma-next-demo uses. prisma-next init currently scaffolds something different (prisma/... at repo root) — that's a defect (TML-2532); the canonical layout is what every command actually expects to see.
- Contract-space-package layout (what you use when publishing a contract-space package — extensions, internal monorepo packages).
prisma-next.config.ts at package root; src/contract.{prisma,ts} directly (no prisma/ subdir); src/contract.{json,d.ts} colocated; migrations/<timestamp>_<slug>/ directly under migrations/ (no <space-id> segment — the package is a single space). Documented in .cursor/rules/contract-space-package-layout.mdc and ADR 212.
Both layouts let defineConfig's contract: path point at the source; the framework derives everything else (emit output, migration root) from there. Pick the layout that matches what you're building and stick with it — don't mix.
Diagnostic codes you route on
prisma-next contract emit surfaces structured errors with stable codes; branch on code rather than message text.
| Code | Meaning | Next move |
|---|
PN-CLI-4002 Contract configuration missing | contract not set in prisma-next.config.ts. | Add contract: './src/prisma/contract.prisma' (app layout) or './src/contract.prisma' (contract-space-package layout) — likewise for .ts sources — to defineConfig({...}) from @prisma-next/postgres/config. |
PN-CLI-4003 Contract validation failed | Source loaded but the Contract IR failed structural validation. | Read meta.diagnostics / meta.issues for the offending model/field, fix the source, re-emit. |
PN-CLI-4011 Missing extension packs in config | The contract uses a namespaced constructor (e.g. pgvector.Vector(...)) but extensions in the config does not list a matching descriptor. meta.missingExtensions names them. | Install the package, import its control descriptor (import pgvector from '@prisma-next/extension-pgvector/control'), add it to extensions: [...] in prisma-next.config.ts. |
Workflow — Read the contract source of truth
The concept: every contract change starts by locating the source file. The config is authoritative — read prisma-next.config.ts, find the contract: field (a path string under the façade), and open the file it points at. The same field tells you the installed extensions: [...].
cat prisma-next.config.ts
If contract: ends in .prisma, the source is PSL; if it ends in .ts, the source is the TS builder. If prisma-next.config.ts is missing, route to prisma-next-quickstart.
Workflow — Edit a model / field / relation (PSL)
The concept: PSL models lower to tables (or collections, on Mongo); fields lower to columns; @relation(...) declares the FK side. Add the relation only on the owning side — the framework derives the back-reference automatically.
model User {
id Int @id @default(autoincrement())
email String @unique
}
model Post {
id Int @id @default(autoincrement())
title String
authorId Int
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
@@unique([title, authorId])
@@index([authorId])
}
Then run pnpm prisma-next contract emit (or rely on the Vite plugin — see prisma-next-build). Specify cascade behaviour explicitly with onDelete / onUpdate; the default is Restrict.
@@index also accepts expression: (instead of a fields list), where: (partial-index predicate), unique:, type:/options: (target-registered access method), and name: xor map::
@@index(expression: "lower(email)", name: "users_email_lower")
@@index([authorId], where: "(archived_at IS NULL)", name: "posts_author_active")
name: declares a managed index (physical name <name>_<8-hex hash>, renames plan as ALTER INDEX … RENAME); map: adopts an exact physical name verbatim (for infer-captured objects — combining it with a SQL body warns, because drift detection byte-compares the authored text against Postgres's reprint). An expression: requires name: or map:. The TS builder mirrors this via constraints.index([cols.x], {...}) / constraints.index({ expression, ... }) — see packages/2-sql/2-authoring/contract-ts/README.md.
PSL alias surface for repeated types lives in a top-level types {} block:
types {
Email = String
}
model User {
id Int @id @default(autoincrement())
email Email @unique
}
Note: scalar lists (e.g. String[]) and implicit Prisma-ORM many-to-many (list nav on both sides without a join model) are rejected by the SQL interpreter — use a join model. Composite/embeddable types (type Address { ... } with address Address on a model) are supported: the interpreter lowers them to valueObjects in the domain and stores them as jsonb columns. See Workflow — Value objects below.
Workflow — Edit a model / field / relation (TS builder)
The concept: same model, different authoring surface. The façade re-exports defineContract, field, model, rel, plus the family/target packs as default exports of @prisma-next/postgres/family and @prisma-next/postgres/target. Use the callback overload (defineContract({...}, ({ field, model, rel, type }) => ({...}))) to get the higher-level helpers (field.text(), field.id.uuidv7String(), field.temporal.createdAt(), type.sql.String(35)).
import sqlFamily from '@prisma-next/postgres/family';
import { defineContract } from '@prisma-next/postgres/contract-builder';
import postgresPack from '@prisma-next/postgres/target';
export const contract = defineContract(
{
family: sqlFamily,
target: postgresPack,
},
({ field, model }) => ({
models: {
User: model('User', {
fields: {
id: field.id.uuidv7String(),
email: field.text().unique(),
createdAt: field.temporal.createdAt(),
},
}).sql({ table: 'app_user' }),
},
}),
);
Then pnpm prisma-next contract emit. The field.<scalar>() helpers are only available inside the callback overload; outside the callback only field.column(...), field.generated(...), field.namedType(...) exist.
For Mongo, swap every @prisma-next/postgres/* import for @prisma-next/mongo/*. The Mongo builder also exposes index and valueObject.
Workflow — Add an extension-typed scalar (pgvector)