The rolling, newest-first index of Prisma 8 releases. Each entry mirrors the release's committed notes file under docs/releases/ (the body of its GitHub Release) under a ## v<version> header — see docs/releases/README.md for the convention and authoring template.
Changelog tracking starts at v0.12.0, the first release cut after this convention landed. For v0.11.0 and earlier, see the GitHub Releases page — historical notes are not backfilled here.
In this release, connect() on the Postgres serverless client returns a connection with db.orm, db.transaction(...) and db.prepare(...), the same members as a postgres() client. A date or time default is stored in one canonical text for each value, so its storage hash no longer depends on how the default was written. The Mongo ORM passes every value it writes, reads or filters on through its field's codec. The Postgres CLI commands now work with date and time defaults on Node 24, which has no Temporal.
The upgrade recipes for this hop: the app recipe and the extension recipe. Each breaking change below names the change id to look for in them.
-
Serverless
connect()returns a connection, not aRuntime.postgresServerless(...).connect({ url })from@prisma/orm-postgres/serverlessnow returns a connection with the members of apostgres()client exceptconnect. Replaceruntime.query(plan)withdb.runtime().query(plan), and passdb.runtime()wherever theconnect()result was used as a runtime.connect()now opens the database connection before it returns, and rejects withDRIVER.CONNECTION_FAILEDwhen the database cannot be reached. Reads no longer go through a server-side cursor by default. Thecursoroption is nowPostgresCursorOptions,{ batchSize?: number }, so deletecursor: { disabled: true }. Seeserverless-connect-returns-connectionandserverless-cursor-default-offin the app recipe. (#30482)Before:
await using runtime = await db.connect({ url: env.HYPERDRIVE.connectionString }); const rows = await runtime.query(db.sql.public.user.select('id').build());
After:
await using db = await postgres.connect({ url: env.HYPERDRIVE.connectionString }); const rows = await db.runtime().query(db.sql.public.user.select('id').build());
-
A date or time default is stored in its type's canonical form. On Postgres and SQLite,
prisma contract emitstores each date or time default as one text for each value:@default("2024-01-01T01:00:00+01:00")on aDateTimecolumn is stored as"2024-01-01T00:00:00Z". TypeScript contracts store the same text. A default that was not already in that form gets a new storage hash. The database does not change: re-emit, then runprisma db sign, or record an empty migration withprisma migration new.contract emitnow refuses a default that the column's type does not hold, such as an offset on aTimestampcolumn, withPSL_INVALID_DEFAULT_LITERAL. Seedate-time-default-stored-in-canonical-form,date-time-default-refused-textanddate-time-ts-default-stored-in-canonical-formin the app recipe. (#30532)Before, accepted and stored as written:
model Event { id Int @id localAt Timestamp @default("2024-01-01T00:00:00Z") }
After, because a
Timestampcolumn holds no offset:model Event { id Int @id localAt Timestamp @default("2024-01-01T00:00:00") }
-
The Mongo ORM checks every value through its field's codec. A write of a value of the wrong type, of a fraction or an out-of-range number to an
Int32field, of a value outside the field's enum, or ofnullto a required field now fails withRUNTIME.ENCODE_FAILEDnaming the field. Before, some of these were stored as given. A filter expression passed towhere()is encoded the same way, so a filter on anInt64field needs abigint.create()andcreateAll()return the document as stored, decoded like a read, and a nullable field missing from a stored document reads asnull, notundefined. The query builder'smatch()still sends values as given. See themongo-*entries in the app recipe. (#30519)Before:
db.orm.posts.where(MongoFieldFilter.gt('views', Long.fromNumber(5)))
After:
db.orm.posts.where(MongoFieldFilter.gt('views', new MongoParamRef(5n)))
-
temporal-polyfillis a peer dependency of the Postgres packages.@prisma/orm-postgresand@prisma/orm-target-postgresnow declaretemporal-polyfillas a required peer dependency. npm, pnpm and bun install it automatically. A project that installs with Yarn must addtemporal-polyfill(^1.0.4) to its own dependencies. Seetemporal-polyfill-is-a-peer-dependencyin the app recipe. (#30520) -
The toolchain requires
@prisma/cli-engine0.6.2. A project that pins@prisma/cli-engineitself must move the pin from0.6.1to0.6.2. With this engine, hints, warnings and errors print the name of the CLI, as inprisma db migrate, where they used to print a literal{bin}. A script that matches{bin}in the CLI's output must match the CLI name instead. Seeengine-pin-moves-to-0-6-2in the app recipe. (#30503) -
Contract source warnings are diagnostics.
prisma contract emitandprisma contract printreport a source warning, such asPSL_DEPRECATED_SCALAR_NAME, as awarndiagnostic of the result instead of a free-textwarning …line. With--json, it is in thediagnosticsof the result. A script that read the old lines must readdiagnosticsinstead. Seecontract-source-warnings-are-diagnosticsin the app recipe. (#30519) -
Extension authors: Mongo result shapes, insert results and codecs changed.
contractModelToMongoResultShapetakesincludes, a map from relation name to the shape of the included document, instead ofincludeRelationNames.compileMongoQuerytakes the contract's value objects as a fifth argument.InsertOneResultandInsertManyResultcarry the inserted documents as stored, so a Mongo driver of your own must yield them. Themongo/double@1codec'sencodereturns the driver'sDouble, and the other Mongo codecs refuse a value of the wrong type.reportUnknownFieldPresettakes theauthoringContributions. See the extension recipe. (#30519)Before:
contractModelToMongoResultShape(model, { includeRelationNames: ['author'] });
After:
contractModelToMongoResultShape(model, { includes: { author: authorShape }, valueObjects });
- A serverless connection has
db.orm,db.transaction(...)anddb.prepare(...). Inside a request, code written for apostgres()client works on the connection thatpostgres.connect({ url })returns, so a hand-builtorm({ runtime, context })andwithTransaction(runtime, fn)are no longer needed. The serverless client also gainsraw,enumsandnativeEnums. (#30482) postgres()takes acursoroption.postgres({ ..., cursor: { batchSize: 100 } })reads through a server-side cursor in batches, aspostgresServerless()does with the same option. Without the option, reads are buffered. (#30482)- The language server reads a multi-file schema as one project. When
contractinprisma.config.tsnames a pattern such as./*.prisma, files you have not opened contribute their models to the others and receive diagnostics. Before, the server read the pattern as a literal file path. (#30456)
- On Node 24, which has no
Temporal,prisma contract emit,db init,db update,contract infer,node migration.ts, the Vite plugin and the language server work on a Postgres schema with a date or time default. Before, they failed with a message that the runtime has no globalTemporalimplementation. (#30520) - A Mongo
Doublefield stores a whole number as a BSONdouble. Before, the write failed withDocument failed validation.ObjectId[],Int64[],Decimal128[]andBinary[]fields can be written, and a query-builder filter that compares with anObjectId,Long,Decimal128orBinarymatches the stored value. (#30519) - Mongo reads decode documents from
include()and composite-type fields through their codecs, so anObjectIdin them reads as a hex string and anInt64as abigint. (#30519) - A Mongo
upsert()whosecreatesets a field that has an update default, such astemporal.updatedAt(), inserts thecreatevalue. Before, the insert got the current time. (#30519) prisma db updateon MongoDB can confirm and apply a destructive change. A validator change that only admits more values, such asJsontoBson, is no longer called destructive. (#30519)- On SQLite, a new
DateTimecolumn's default is the same text the application writes for the same instant, so rows that took the default compare and sort correctly against rows the application wrote. (#30532) - When two namespaces declare models with the same name, each relation points to the model in its own namespace. Before, a relation could point to the same-named model in another namespace. An unknown field type reports one diagnostic instead of two. (#30478)
- A Postgres
policy_*block whose target model is declared outside the block's namespace is stored in the namespace of the table it protects. (#30381) - When
prisma db signrefuses because the database does not match the contract, it offers both ways out: change the database withprisma db update, or change the contract source to describe the database and emit again. It says which one changes the database. (#30438) - The
8.0.0-rc.12to8.0.0-rc.13upgrade guides include a script that renames the default references in migration snapshots, in place of the renaming by hand. (#30453)
This release brings MongoDB closer to Postgres: a Mongo schema can declare Int64, Decimal128, Binary, Json and Bson fields and automatic timestamps, and a Prisma 6 MongoDB project can use its existing schema.prisma as the contract source. The ORM client can order by a related row's column, by a relation count, and with explicit null placement. The new prisma contract print command writes any configured contract as Prisma 8 PSL. A TypeScript contract now encodes every literal default through the column's codec, and the generated defaults in contract.json name their target with new keys, so re-emit your contract after upgrading.
The upgrade recipes for this hop: the app recipe and the extension recipe. Each breaking change below names the change id to look for in them.
-
Generated defaults in the contract name an
entryand afield. Each entry underexecution.mutations.defaultsincontract.jsonused to name its target asref: { namespace, table, column }. It now usesref: { namespace, entry, field }with the same values. Runprisma contract emitafter upgrading; the runtime refuses a contract that still has the old keys. Contract snapshots undermigrations/snapshots/need the same rename by hand. TheexecutionHashchanges, but no migration ordb signis needed. Seeexecution-ref-entry-fieldin the app recipe. (#30399)Before:
{ "ref": { "namespace": "public", "table": "user", "column": "updated_at" }, "onUpdate": { "kind": "generator", "id": "timestampNow" } }After:
{ "ref": { "entry": "user", "field": "updated_at", "namespace": "public" }, "onUpdate": { "kind": "generator", "id": "timestampNow" } } -
A literal
.default(value)in a TypeScript contract must be the codec's input type.defineContractfrom the Postgres and SQLite packages now encodes every literal default through the column's codec. A value of the wrong type is a type error for fields built inside thedefineContractfactory, and a value the codec refuses fails the build withCONTRACT.DEFAULT_INVALID. Pass a value of the codec's input type, or choose the field preset whose codec takes the value you have.bigintand bytes defaults are stored in a different form, which changes the storage hash of a contract that has one. PSL contracts,now(),autoincrement()andsqltagged defaults are not affected. Seets-defaults-encoded-by-codecin the app recipe. (#30433)Before:
createdAt: field.dateTime().default('2024-01-01T00:00:00Z'), views: field.bigint().default(1),
After:
createdAt: field.temporal.timestamptzString().default('2024-01-01T00:00:00Z'), views: field.bigint().default(1n),
-
cursor()refuses an order that is not a plain column.cursor()throwsORM.ARGUMENT_INVALIDwhen an activeorderByitem is an extension-operation result (such as a vector distance), a relation field, a relation count, or an order with null placement. Before, an extension-operation order was left out of the keyset without an error, which returned wrong pages. Paginate such queries withlimit()andoffset(), or order by plain columns only.distinctOn()throws the same error when one of its leading orders is not a plain column. Seecursor-rejects-expression-ordersin the app recipe. (#30402) -
A hand-written contract source in
prisma.config.tsmust declare itsformat. A config that buildscontract.sourceitself, as an object with aloadfunction, must give itformat: 'psl'orformat: 'typescript'. Without it, every command that reads the config fails withCONFIG.VALIDATION_FAILED. Sources made bydefineConfig,prisma7Schema(),prismaContract()and the TypeScript contract helpers already declare one. Seeconfig-contract-source-requires-formatin the app recipe. (#30315) -
prisma contract formatformats a Prisma 7 schema, and policy expressions decode every JSON escape. A project whose contract isprisma7Schema(...)used to be skipped byprisma contract format; the command now formats that file with the Prisma 8 formatter. Do not run it on a schema that must keep Prisma 7's formatting. AusingorwithCheckexpression in a PSLpolicy_*block now also decodes\t,\b,\f,\/and\uXXXX; write the backslash twice if you mean the backslash and the letter. Seecontract-format-formats-prisma7-schemaandpolicy-expression-json-escapesin the app recipe. (#30315) -
The Mongo codec subpaths moved from the adapter to the target. The
adapter/codec-types,adapter/codecs,adapter/codec-idsandadapter/data-typessubpaths of@prisma/orm-mongoand@prisma/orm-target-mongoare nowtarget/.... An emitted Mongocontract.d.tsimportsadapter/codec-types, so re-emit the contract and rewrite the import in eachcontract.d.tsundermigrations/snapshots/.createMongoRunnerDeps(...)is removed,MongoRunnerDependenciesandMarkerOperationsmoved to@prisma/orm-mongo/family/control-adapter, and a Mongo family instance must be created from a control stack that includes the adapter. The contract JSON and every hash stay the same. See themongo-*entries in the app recipe and the extension recipe. (#30396)Before:
import type { CodecTypes } from '@prisma/orm-mongo/adapter/codec-types';
After:
import type { CodecTypes } from '@prisma/orm-mongo/target/codec-types';
-
Fields of a Mongo variant model go through their codecs. Through
.variant(...), a field declared only on the variant model is now written and read through its codec, as base-model fields always were: anObjectIdfield is stored as anObjectIdand read back as a hex string, and a where filter on it encodes a hex string. Remove any code that converted such values by hand. A contract built with the TypeScript builder has no collection validator, so it may have stored such values as strings; the recipe converts them. Seemongo-variant-field-codecsin the app recipe, andmongo-bson-codec-addedin the extension recipe. (#30439) -
Four Mongo PSL scalar names are deprecated. A Mongo schema now names each scalar after the BSON type it stores:
IntbecomesInt32,FloatbecomesDouble,BooleanbecomesBoolandDateTimebecomesDate. The old names still work and produce the same contract, but each use reports aPSL_DEPRECATED_SCALAR_NAMEwarning, and a later release removes them. Postgres and SQLite schemas do not change. Seemongo-psl-scalar-namesin the app recipe. (#30396)Before:
model Post { id ObjectId @id @map("_id") views Int rating Float? published Boolean createdAt DateTime }
After:
model Post { id ObjectId @id @map("_id") views Int32 rating Double? published Bool createdAt Date }
-
Extension authors: mutation defaults and temporal presets moved to the framework.
GeneratorStability,RuntimeMutationDefaultGenerator,MutationDefaultsOptions,AppliedMutationDefaultandMutationDefaultsOpare now exported from@prisma/orm-framework/components/runtime.applyMutationDefaultstakesentryinstead oftable, and each applied default names itsfieldinstead of itscolumn.TIMESTAMP_NOW_GENERATOR_ID,temporalAuthoringPresetsandtemporalCodecPresetmoved to@prisma/orm-framework/components/authoring, andtimestampNowControlDescriptormoved to@prisma/orm-framework/components/control.MongoExecutionContexthas a new requiredapplyMutationDefaultsmethod. See the extension recipe. (#30406, #30403)Before:
const applied = context.applyMutationDefaults({ op: 'create', table: tableName, namespace, values }); for (const def of applied) row[def.column] = def.value;
After:
const applied = context.applyMutationDefaults({ op: 'create', entry: tableName, namespace, values }); for (const def of applied) row[def.field] = def.value;
-
Extension authors:
OrderByItemcarries a null placement, andIncludeExprcarries key column lists. TheOrderByItemconstructor takes a required third argument,nulls, and a renderer that writesORDER BYitself must writeNULLS FIRSTorNULLS LASTafter the direction.IncludeExpr.localColumnandIncludeExpr.targetColumnare now the arrayslocalColumnsandtargetColumns, paired by index. Seeorder-by-item-nullsandinclude-expr-join-column-listsin the extension recipe. (#30402, #30107)
- Order by a related row's column, a relation count, and null placement. Inside
orderBy, a to-one relation offers the related model's columns (post.author.name.asc()), a to-many relation offerscount()with an optional filter (user.posts.count().desc()), and everyasc()anddesc()accepts{ nulls: 'first' | 'last' }. (#30402) prisma contract printwrites the configured contract as Prisma 8 PSL. The command loads whatevercontractnames in the config (a Prisma 7 schema, a TypeScript contract or a PSL contract) and prints it as a Prisma 8 PSL file that emits the same contract, including its hashes. It refuses, by name, any part of the contract that PSL cannot express. Use--output <path>to write a file. (#30315)- A Prisma 6 MongoDB project can use its existing
schema.prismaas the contract source. Setcontract: prisma6Schema('prisma/schema.prisma')withprisma6Schemafrom@prisma/orm-mongo/config. Anything the reader cannot express is reported as an error with aPSL.PRISMA6_MONGO_*code. (#30405) - Mongo schemas can declare
Int64,Decimal128,Binary,JsonandBsonfields. The ORM reads the first four asbigint, decimal text,Uint8Arrayand a JSON value; aJsonfield refuses a value that is not JSON, at any depth. ABsonfield holds any BSON value. The TypeScript helpers arefield.int64(),field.decimal128(),field.binary(),field.json()andfield.bson(). (#30396, #30439) - Mongo schemas can declare automatic timestamps.
temporal.createdAt()andtemporal.updatedAt()fill the field on create, andtemporal.updatedAt()advances it on every update that writes something. (#30403) prisma contract inferprints Postgres array defaults as literal lists. A default such as'{a,b}'::text[]on a text, varchar, enum, date or boolean array column now prints as@default(["a", "b"])instead of a rawsqlexpression. (#30436)
- An ORM
include()across a composite foreign key matches on every key column. It used to match on the first column only, which returned related rows that belonged to other parents. Nested writes and multi-table variants use the whole key too. (#30107) - A Mongo ORM query that combines
select()withinclude()returns the included relations. (#30170) mongo()accepts a connection string that lists several hosts, and the CLI masks the credentials of such a string in its output. (#30354)- A Postgres migration that removes a column and a row-level security policy that references it drops the policy first, so the migration applies. (#30232)
limit()andoffset()refuse a value that is not a non-negative integer, such asNaN, withRUNTIME.AST_INVALID, instead of writing it into the SQL. (#30133)- The Postgres warning for an identifier that is too long measures the name in bytes, as Postgres does, so it now fires for a long name written in non-ASCII characters. (#30127)
- A number inside a
json[]orjsonb[]array default, such as'{1,true}'::jsonb[], is read as a JSON number, not as text. (#30455) createAll(rows, { onConflict: 'skip' })on a variant stored in its own table reports an error that names the unsupported option, and the help forprisma migration new --fromnames the correct default, thedbref. (#30427)
- @rajat12826 made their first contribution in #30170
- @xia-chao made their first contribution in #30133
- @MahathirMohammadShuvo made their first contribution in #30127
- @Punisheroot made their first contribution in #30232
This release adds Postgres full-text search, prepared ORM reads and aggregates, schemas split across several files, and a way for a Prisma 7 project to use its existing schema.prisma as the Prisma 8 contract source. It also changes how a schema is written: a model without @@map now names its table exactly as written, every schema file needs // use prisma-8 as its first line, dbgenerated(...) is replaced by sql tagged literals, and a column default must be a value its column's data type accepts. The toolchain moves to @prisma/cli-engine@0.6.1, which no longer exports defineConfig.
The upgrade recipes for this hop: the app recipe and the extension recipe. Each breaking change below names the change id to look for in them.
-
The engine peer moves to
@prisma/cli-engine@0.6.1, andprisma.config.tsmust importdefinePrismaConfig.@prisma/orm-toolchainpeers the engine at an exact version, and this release peers 0.6.1 (up from 0.4.0). Projects assembled by theprismaCLI resolve the engine automatically; a project that pins@prisma/cli-engineitself must move the pin to0.6.1. The engine no longer exports the deprecateddefineConfigalias, so a config file that importsdefineConfigfrom@prisma/cli-enginefails to load until it importsdefinePrismaConfig. ThedefineConfighelper from a product package such as@prisma/orm-postgres/configkeeps its name. The new engine also changes how config files are read. It collects everyprisma.config.tsfrom the current directory (or from the file passed to--config) up to the repository root, which is the first directory with a.gitentry, and merges them key by key, with the nearest file winning; a project under a stray parent config now inherits its values, so remove that file or addparent: falseto the project's config. A relative path such ascontractormigrations.dirnow resolves from the directory of the config file that wrote it, not from the working directory. Under theprismaCLI, a malformedormfield is reported asCLI.CONFIG_FIELD_INVALID, naming the field and the file, insideCLI.CONFIG_SECTION_INVALID, where it used to beCONFIG.VALIDATION_FAILED. Seeengine-pin-moves-to-0-6-1,config-paths-resolve-from-declaring-fileanddefine-config-becomes-define-prisma-configin the app recipe. (#30372, #30129, prisma/prisma-cli#233, prisma/prisma-cli#279, prisma/prisma-cli#280, prisma/prisma-cli#284)Before:
import { defineConfig } from '@prisma/cli-engine'; export default defineConfig({ ... });
After:
import { definePrismaConfig } from '@prisma/cli-engine'; export default definePrismaConfig({ ... });
-
A PSL model without
@@mapnames its table exactly as written.model UserProfileused to read and write the table"userProfile". It now uses"UserProfile", and Mongo collections follow the same rule. Before you plan a migration, run theadd-model-mapscript from the upgrade recipe over every.prismafile, including thecontract.prismacopies undermigrations/. It adds@@map("<current table name>")to each model that has none, so the emitted contract, the storage hash and the database stay the same. Run it once, and only on a schema written for an earlier release. If you plan without it,migration plan,db updateandmigratestop withMIGRATION.TABLE_NAME_CASE_CHANGEDinstead of dropping the table and creating an empty one. Mongo has no planner, so an unmapped model reads an empty collection without any error; run the script before you deploy.contract inferfollows the same rule, so a table already named"UserProfile"now infers without@@mapand verifies clean. Seepsl-model-names-table-verbatimin the app recipe and the extension recipe. (#30317, #30321)Before:
model UserProfile { id Int @id email String }
After:
model UserProfile { id Int @id email String @@map("userProfile") }
-
Every PSL schema file needs
// use prisma-8as its first line.contract emitnow reads only the files that carry this header, which is how a schema split across several files knows its members (see Features). A file without it is left out of the contract without a warning, and when no file has it, emit fails withPSL_NO_OPTED_IN_SCHEMA_FILES. The older// use prisma-nextheader still counts.orm initalready writes the header, and the upgrade recipe has a script that adds it to every file that lacks it. Seepsl-schema-requires-use-prisma-8-directivein the app recipe. (#30379) -
dbgenerated(...)is removed, and a raw SQL default is written as asqltagged literal.@default(dbgenerated("..."))now fails withPSL_UNKNOWN_DEFAULT_FUNCTION, and the message names the replacement. Writenow()andautoincrement()as the named functions, a JSON value as ajsonliteral, an enum member or a text value as a quoted string, and any other SQL as@default(sql`...`).sql`now()`andsql`autoincrement()`are refused.contract inferprints raw defaults in the new form. The JSON and enum rewrites change the default incontract.jsonfrom an expression to a literal, so the storage hash moves; the live default already matches, so no migration is needed. In the TypeScript contract builder,.defaultSql('...')is deprecated and will be removed in 8.0.0: write.default(now()),.default(autoincrement())or.default(sql`...`)instead. A Prisma 7 schema read throughprisma7Schemakeeps itsdbgenerated. Seedbgenerated-removed-from-pslanddefault-sql-method-deprecatedin the app recipe and the extension recipe. (#30325, #30380, #30347)Before:
id String @id @default(dbgenerated("gen_random_uuid()")) createdAt DateTime @default(dbgenerated("now()")) expiresAt DateTime @default(dbgenerated("(now() + '00:03:00'::interval)"))
After:
id String @id @default(sql`gen_random_uuid()`) createdAt DateTime @default(now()) expiresAt DateTime @default(sql`(now() + '00:03:00'::interval)`)
-
A written default must be a value its column's data type accepts. Every value written in PSL now has a data type, decided by how it is written, and a column takes it only if the column's type accepts that type. A quoted string therefore no longer works as a JSON, decimal or float default. Write a JSON default as a
jsonliteral, a decimal as a bare number, andNaNandInfinitywithout quotes. A list default on a column that holds one JSON value is onejsonliteral, such as@default(json`[1, 2]`). A refused default fails withPSL_DEFAULT_TYPE_INCOMPATIBLEand names the types the column accepts. Numbers now keep every digit: aDecimalorNumericdefault emits as decimal text ("1.50"), and aBigIntdefault larger than 2^53 now emits instead of failing. A contract with such a default gets a new storage hash, so re-emit it and runprisma db sign. The same applies to aBigIntNumbercolumn (pg/int8number@1orsqlite/bigintnumber@1) with a literal default, which now stores digit text.contract inferprints each default in a formcontract emitreads back. Seea-json-default-is-a-json-tag,a-decimal-default-is-written-unquoted,a-float-non-finite-default-is-written-bare,a-json-list-default-is-one-json-literal,number-valued-64-bit-columns-store-their-default-as-digit-textandpsl-number-defaults-keep-digitsin the app recipe. (#30350, #30287)Before:
meta Jsonb @default("{}") price Decimal @default("1.50") ratio Float @default("NaN")
After:
meta Jsonb @default(json`{}`) price Decimal @default(1.50) ratio Float @default(NaN)
-
Creation timestamp presets use the application clock.
temporal.createdAt()andtemporal.createdAtString(), and the matchingfield.temporal.*helpers, no longer declare a database default. The ORM sets the value on create, from the same clock as the matchingupdatedAtpreset. Re-emit the contract and apply a migration that removes the old database defaults. After that, code that inserts rows with raw SQL must supply the timestamp itself. To keep a database-generated value, use an explicit timestamp type with@default(now()). A preset backed by Temporal now needs a globalTemporalbefore writes as well as reads. Seeclient-generated-created-at-presetsin the app recipe. (#30330) -
Re-emit Postgres contracts: the query operation types moved from the adapter to the target. The emitted
contract.d.tsnow importsQueryOperationTypesfrom@prisma/orm-postgres/target/operation-types. The old subpath,@prisma/orm-postgres/adapter/operation-types, is gone, so acontract.d.tsemitted by an earlier release stops type-checking until you runprisma contract emit. Change any import of the old subpath in your own code the same way.contract.jsondoes not change. Seere-emit-the-contract-for-the-moved-query-operation-typesin the app recipe. (#30348) -
preparecallbacks on the Postgres and SQLite clients receive only the params. The callback no longer gets a SQL builder as its first argument; use the client's own.sqlproperty instead. Calls to.query(target, params)do not change. Seeparams-only-sql-facade-preparein the app recipe. (#30260)Before:
const query = await db.prepare({ id: 'pg/int4@1' }, (sql, params) => sql.public.users.select('id').where((f, fns) => fns.eq(f.id, params.id)).build(), );
After:
const query = await db.prepare({ id: 'pg/int4@1' }, (params) => db.sql.public.users.select('id').where((f, fns) => fns.eq(f.id, params.id)).build(), );
-
Native Postgres enum columns no longer offer text operations. Postgres has no
LIKE,ILIKEor text search for an enum type, solikeandilikeon a native enum column always failed when the query ran. They are now type errors, the new full-text operations do not accept such a column, and@@fullTextIndexon it is refused when the contract is built. Compare the column witheqorininstead. An enum stored as text (@@type("pg/text@1")) keeps every text operation. Seenative-enum-columns-have-no-text-operationsin the app recipe. (#30390) -
The Postgres target decodes list columns. Enum list columns now read back as arrays on every path, including
create()results, without a cast in the SQL. Two values change. An element of a fixed-scale numeric list reads the way Postgres prints it: anumeric(30,10)[]element written as1.5reads as"1.5000000000". A row read directly through the lower-level Postgres driver returns a list column as raw Postgres array text, such as'{a,b}'. ORM and SQL builder reads still return JavaScript arrays. Update assertions and snapshots that pin those values. Seepostgres-target-owned-list-framingin the app recipe. (#30235) -
migration newpicks its starting point the waymigration plandoes, and three error codes are removed. Without--from,migration newused to build on the newest migration. It now starts from thedbref, or from an empty database when there are no migrations, and otherwise refuses withMIGRATION.PLAN_ORIGIN_UNKNOWN. Adbref on an empty migration graph is refused with a pointer tomigration plan, which writes the baseline. Pass--fromin scripts that relied on the old default. The CLI no longer looks for a single newest migration, so a migration history with two branches now reports the real error, such asMIGRATION.HASH_NOT_IN_GRAPH.MIGRATION.AMBIGUOUS_TARGET,MIGRATION.NO_TARGETandMIGRATION.NO_INITIAL_MIGRATIONare removed, andgraphTipandgraphTipHashare no longer in the JSONmetaof the errors that carried them. Seemigration-new-defaults-to-the-db-refandmigration-tip-error-codes-removedin the app recipe. (#30389) -
The Supabase extension's contract changed, so re-sign databases that use it.
@prisma/orm-extension-supabasenow declares the two nullable list columns it used to leave out (storage.buckets.allowed_mime_typesandstorage.objects.path_tokens), the 43 check constraints of its reference Supabase build, its native enum defaults as member values, and its JSON defaults asjsonliterals. Its storage hash changes, so runprisma db signagainst every database signed with the previous version; if you re-emit your own contract, do that first. Your owncontract.jsondoes not change. If your Supabase build's check constraints differ from the reference build (supabase/postgres 17.6.1.106),db verifynow reports the missing ones. Seesupabase-contract-declares-nullable-list-columnsandsupabase-contract-regenerated-from-the-reference-fixturein the extension recipe. (#30318, #30346, #30380) -
Changes for extension authors. These affect packages built on
@prisma/orm-framework, the@prisma/orm-family-*packages and@prisma/orm-toolchain. Each item names its change id in the extension recipe, except the last.- Every codec descriptor names the data type it represents in a required
dataType, and a pack registers its data types, with their casts, throughdataTypeson its component metadata. Casts replaceliteralTypesand each codec's list of accepted shapes,decodeJsontakes only the data type's canonical form, and PSL support for a data type is an authoring entry underauthoring.dataTypes. Seeevery-codec-descriptor-names-a-data-typeand the entries that follow it. (#30350) - A codec without params sets
paramsSchematoundefined, andvoidParamsSchemais removed (codec-without-params-has-no-params-schema). (#30372) - An extension that pins
@prisma/cli-enginemoves the pin to0.6.1, and a config section'svalidatereceives a secondprovenanceargument (engine-pin-moves-to-0-6-1). (#30372) emit()from@prisma/orm-toolchain/emitterrequires adeserializeContractoption and writescontract.d.tsin the order ofcontract.json(emit-requires-deserialize-contract). (#30319)QueryOperationTypesmoves from the Postgres adapter to the Postgres target (query-operation-types-move-to-the-postgres-target). (#30348)SqlLoweringSpecloses its unusedstrategyfield; delete it from operation descriptors (sql-lowering-spec-drops-strategy). (#30373)pg/enum@1no longer has thetextualtrait, so an operation declared ontextualno longer attaches to native enum columns (native-enum-codec-is-not-textual). (#30390)- For prepared queries, an expression's codec moves to
returnType.codec, ORM preparation uses the sharedPreparabletype,PreparedParamRefkeeps its declared nullability, and the limit and offset inCollectionStateandGroupPagingStatecan be expressions (expression-codec-on-return-type,shared-preparable-envelope,preserve-prepared-reference-nullability,preserve-orm-pagination-expressions,preserve-grouped-orm-pagination-expressions). (#30260, #30309) - A Postgres codec used for list columns receives each element as raw text (
postgres-list-element-codecs-receive-raw-strings). (#30235) parseRawDefaultis no longer exported fromfamily/psl-infer; importparsePostgresDefaultfrom@prisma/orm-postgres/target/default-normalizer(psl-infer-raw-default-parser-is-target-owned). (#30287)- Code that runs several inserts for one logical create passes one
defaultValueCacheto all of them (share-create-default-cache-across-inserts). (#30330) - The PSL parser API changed.
fieldAttribute,modelAttributeandblockAttributerequiredocumentation, andidentifier(name)takes{ documentation }as a second argument.entityRef()takes a selector, such asentityRef({ kind: 'model' }), and returns the declaration it resolved; useidentifier()for a name that is not checked.parse()requires a file name as its second argument, and the interpreter input takes adocumentslist in place ofdocument. Seepsl-attribute-specs-are-documented,psl-entity-ref-takes-a-selectorandpsl-parse-takes-a-file-namein the extension recipe. (#30312, #30344, #30335, #30379)
- Every codec descriptor names the data type it represents in a required
-
Postgres full-text search. Text columns gain
fullTextMatches,fullTextRankandfullTextHeadlinein the ORM and the SQL builder. The query argument is atsquery, built withwebsearchToTsquery,plaintoTsquery,phrasetoTsqueryortoTsqueryfrom@prisma/orm-postgres/target/full-text, or with thetsquerytemplate tag, which turns each interpolated value into one quoted term so user input cannot add operators. A bare string is a type error.@@fullTextIndex([field])in PSL, orfullTextIndex(cols.field)in the TypeScript contract builder, creates the GIN index these queries use. Give the index and the operation the samelanguage; otherwise Postgres does not use the index.examples/prisma-8-demosearches post titles end to end. (#30348, #30386, #30376)model Post { id Int @id title String @@fullTextIndex([title], name: "post_title_search") }
import { websearchToTsquery } from '@prisma/orm-postgres/target/full-text'; const q = websearchToTsquery(input); const posts = await db.orm.public.Post.select('id', 'title') .where((p) => p.title.fullTextMatches(q)) .orderBy((p) => p.title.fullTextRank(q).desc()) .all();
-
A Prisma 7 schema as the contract source, on Postgres.
prisma7Schema('prisma/schema.prisma')from@prisma/orm-postgres/configreads a Prisma 7 schema directly, so Prisma 8 can run beside Prisma 7 on the database Prisma 7 migrates.contract emitanddb signwork as usual; run both again after each Prisma 7 migration. A construct Prisma 8 cannot describe exactly, such as aview, is an error that names the line and a Prisma 7 edit that removes it.prisma orm init --from-prisma7-schema prisma/schema.prismasets this up, and a plainprisma orm initin a Prisma 7 project offers to. It checks that Prisma 8 can read the schema before it changes anything, keeps Prisma 7 installed as@prisma/prisma7with its config renamed toprisma7.config.tsand its scripts pointed atprisma7, and writes the Prisma 8 config and client undersrc/prisma/. It does not touchprisma/or the database. (#30287, #30291)import { definePrismaConfig } from 'prisma/config'; import { defineConfig as ormConfig, prisma7Schema } from '@prisma/orm-postgres/config'; export default definePrismaConfig({ orm: ormConfig({ contract: prisma7Schema('prisma/schema.prisma'), db: { connection: process.env['DATABASE_URL']! }, }), });
-
Schemas split across several files. The
contractoption accepts a glob such as'./prisma/**/*.prisma'. Every matching file that starts with// use prisma-8becomes part of one schema, and a new file joins it on the next emit without a config change. The default output goes in the glob's fixed directory (./prisma/contract.json), andorm formatformats every file. Namespace blocks with the same name in one file now merge into one namespace. (#30379, #30343) -
Prepared ORM reads and aggregates. Inside
db.prepare(...), an ORM query can end in.prepared.all(),.prepared.first()or.prepared.aggregate(...), on ordinary and grouped collections. The query is built once. Each.query(target, params)call runs it with new values against the runtime, connection or transaction you pass, and returns the same result shape as the ordinary call. Reading included relations also does less work per row. (#30260, #30309, #30289, #30284)const byId = await db.prepare({ id: 'pg/int4@1' }, (p) => db.orm.public.User.select('id').prepared.first({ id: p.id }), ); await byId.query(runtime, { id: 2 }); // { id: 2 }
-
createAllandcreateAndCountcan skip rows that collide with a unique constraint. Pass{ onConflict: 'skip' }, and optionallyconflictOn: ['email']to name the constraint.createAllreturns only the rows the database wrote, andcreateAndCountcounts only those. Postgres and SQLite support it; multi-table inheritance variants refuse it. Re-emit your contract before you use it: the option needs two new capabilities that a contract from an earlier release does not list (re-emit-for-the-insert-conflict-skip-capabilitiesin the app recipe). (#30365) -
JavaScript
Datetimestamps on Postgres.TimestamptzJsDate(p)in PSL,field.temporal.timestamptzJsDate()in TypeScript, and thecreatedAtJsDate()andupdatedAtJsDate()presets read and writeDatevalues, with no Temporal polyfill. ADatekeeps milliseconds only. (#30288) -
Editor support for attribute arguments. The language server shows signature help for attribute arguments, completes values inside nested arguments (lists, records, function calls and field references), names the placeholders in its snippets, and suggests only scalar fields where an attribute expects one. (#30312, #30266, #30329)
-
Each finding when a contract source fails to load.
CONTRACT.SOURCE_LOAD_FAILEDcarries adiagnosticsarray, with one entry per finding giving its code, its summary and, where known, its file and line. The terminal prints them.meta.diagnosticsandmeta.issuesare unchanged. (#30287)
- A command that reads a migration snapshot now checks that the file's content still matches the hash it is filed under, and stops with
MIGRATION.CONTRACT_SNAPSHOT_CONTENT_MISMATCHif the file was edited.migration checkreports the same problem asMIGRATION.CHECK_SNAPSHOT_CONTENT_MISMATCH. Before, an edited snapshot could makemigration planreport no changes. This covers SQL targets; Mongo snapshots are not checked yet. (#30086) migration planwarns when planning from thedbref would branch the migration history, and asks for consent before it writes a baseline with destructive operations, the waydb updatedoes (in scripts, pass--no-interactive --confirm <directory>).migration new --fromnow refuses a hash on an empty migrations directory, and a hash prefix that matches more than one migration, instead of ignoring them. (#30084)db init,db update,db sign,migrate,migration planandmigration newno longer needcontract.d.tson disk. They render the snapshot's types fromcontract.json, and refuse withCONTRACT.TYPES_RENDER_FAILEDbefore writing to the database if that fails. Before, a missingcontract.d.tsletdb initchange the database and then exit on a file error without setting the ref. Apackage.jsonthat depends on both@prisma/orm-postgresand@prisma/orm-mongois now reported asCLI.PROJECT_MANIFEST_INVALID. (#30293, #30298)contract emitwritescontract.d.tsin the order ofcontract.json, so your next emit reorders the models, fields and relations in that file and changes nothing else. (#30298, #30319)contract inferprints a nullable Postgres list column asType[]?instead of as a required list. (#30313)db verifyon Postgres reads more default forms as values: negative and cast numbers, enum values cast to a type in another schema,timestampvalues without a time zone, andARRAY[...]lists. Columns reported as different for these now verify clean. Introspection now reads with fixed session settings (TimeZone = UTC, ISO dates), so a contract inferred from a server outside UTC may show one difference in atimestamptzvalue inside a check constraint or index predicate; re-emit and re-sign once. (#30287)- A "now" value that the ORM generates for a
timestampcolumn without a time zone, such astemporal.timestamp(onUpdate: now), no longer fails at write time. (#30287) createAndCountreturns the number of rows the database inserted, not the length of the input array. (#30365)- The TypeScript contract builder reports a type error at
defineContractwhen a model's ids, uniques, indexes or foreign keys share a name. The check existed but never fired, so a contract that reuses a name now fails to type-check. (#30373, #30387) - In the TypeScript contract builder, a foreign key to a model in another contract space whose
.sql()stage is a function is now an authoring error (CONTRACT.FOREIGN_KEY_INVALID). Before, it produced aREFERENCESclause to a guessed lowercase table name. Give the target model a static.sql({ table: '...' }). (#30323) @@base(...)can name a model declared later in the file. An argument that names no model, or names something that is not a model, is reported at the argument asPSL_INVALID_ATTRIBUTE_SYNTAX;PSL_BASE_TARGET_NOT_FOUNDis removed. (#30344)--confirmnow works in an interactive terminal, and a command that prompted exits when it finishes instead of waiting for a key press. (prisma/prisma-cli#283)- The bundled
prisma-8agent skill: its upgrade references name the published@prisma/orm-*packages, its CI guidance deploys with onedb migratecommand, and its migration reference says thatmigration newrefuses adbref on an empty migration graph. (#30283, #30382, #30391)
This release moves the toolchain onto @prisma/cli-engine@0.4.0, which adds a Markdown output format to every CLI command. Nothing else changed since rc.10.
The upgrade recipes for this hop: the app recipe and the extension recipe.
- The engine peer moves to
@prisma/cli-engine@0.4.0—@prisma/orm-toolchaindeclares the unified CLI's engine as an exact peer, and this release peers 0.4.0 (up from 0.3.0), so a project that pins the engine itself must change its pin. Under a host CLI running on that engine, every command supports--format markdown, which prints the command's output as Markdown; the engine'sFormattype widens from"human" | "json"to"human" | "json" | "markdown". No other public API changed. Projects assembled by theprismaCLI resolve the engine automatically; a project that pins@prisma/cli-engineitself must move the pin to0.4.0. (prisma/prisma-cli#260)
This RC finishes the rename from Prisma Next to Prisma 8 in every identifier a project can see (the old schema header keeps working, the old environment variables do not), adds named model and result types to the emitted contract, makes db sign set the db ref so the first plan after adoption stays incremental, and adds attribute completion to the language server.
-
CLI environment variables lose the
NEXT_infix.PRISMA_NEXT_DISABLE_TELEMETRY,PRISMA_NEXT_TELEMETRY_ENDPOINT,PRISMA_NEXT_DEBUG, and the rest are nowPRISMA_DISABLE_TELEMETRY,PRISMA_TELEMETRY_ENDPOINT,PRISMA_DEBUG, and so on. Only the oldPRISMA_NEXT_DISABLE_TELEMETRYopt-out is still honoured; rename the others in shell profiles,.envfiles, and CI. The per-user telemetry config moves from~/.config/prisma-next/to~/.config/prisma-8/, so the one-time telemetry notice prints once more.orm initnow writes its primer asprisma-8.mdinstead ofprisma-next.md. See the app upgrade recipe. (#30262) -
contract emitrejects a relation field whose?disagrees with its foreign key. A required relation field over a nullable foreign key (author UserwithauthorId Int?), or an optional field over a required key, now fails emission where rc.9 accepted it. Make the two agree. Existing contracts are not affected until you next emit; acontract.jsonfrom an earlier release still loads unchanged. Extension authors:ContractNonJunctionRelation's'1:1'and'N:1'members now require anullableboolean, and the emitter refuses a contract space whosecontract.jsonlacks it until the space is rebuilt. See the app upgrade recipe and the extension upgrade recipe. (#30231)Before:
authorId Int? author User @relation(fields: [authorId], references: [id])After:
authorId Int? author User? @relation(fields: [authorId], references: [id])
-
The schema header is now
// use prisma-8, and// use prisma-nextis deprecated.orm initandcontract inferwrite the new header. The old one still works:contract emitnever reads the header, and the language server still recognises it and rewrites it to the new form when you format the file. Replace it at your convenience; the app upgrade recipe does it for you. (#30262) -
Named model and result types.
contract.d.tsexports aModelsnamespace and amodelsconstant with one member per model (Models.public_User, ortypeof models.public.User; bare names on SQLite).Scalars<M>names the row a default fetch returns,Shape<M, Spec>derives a data structure with chosen scalars and nested relations, andResultTypenow works on ORM queries instead of returningnever. Both come from@prisma/orm-postgres/family-contract/types(or the@prisma/orm-mongoequivalent). These replace Prisma 7'sPrisma.UserandUserGetPayload<...>. To get them, runprisma contract emitonce after upgrading: the re-emit also records each to-one relation's nullability incontract.jsonas anullableboolean, which is what theModelstypes are built from. (#30231)import type { Models } from './prisma/contract'; import type { Scalars, Shape } from '@prisma/orm-postgres/family-contract/types'; import type { ResultType } from '@prisma/orm-postgres/components/runtime'; type UserRow = Scalars<Models.public_User>; type UserResponse = Shape<Models.public_User, { '-': 'passwordHash'; posts: { '+': 'id' | 'title' } }>; const usersWithPosts = db.orm.public.User.include('posts'); type UserWithPosts = ResultType<typeof usersWithPosts>;
-
Attribute completion in the language server. Editors now complete field, model, and block attribute names and their named argument keys from the installed target and extensions, and insert required arguments as editable snippets where the editor supports them. (#30249)
-
db signcan choose or skip the ref it advances.--advance-ref <name>writes another ref thandb,--no-advance-refsigns without writing any ref or snapshot, and--jsonoutput gainsadvancedRef: { name, hash }(ornull). (#30251)
- After
db sign,migration planproposes only the change instead of recreating every table.db signnow sets thedbref to the signed contract, even when the database is named with--db, so adopting an existing database no longer needs a baseline plan, a second sign, and a manualmigration ref set. When no ref is set and no migrations exist,migration planprints a notice that it is planning from an empty database, and--jsongainsfromDefaulted: true. (#30251) - Buffered PostgreSQL queries release their pooled connection before rows are decoded or consumed, so a paused result iterator no longer holds a connection and blocks other queries on a small pool. Cursor streams keep their connection until completion; caller-owned connections and transactions are untouched. (#30259)
orm initinstallsprisma@latestinstead ofprisma@next, a dist-tag that no longer exists, so a freshorm initcompletes its install step again. The engine fallback is@prisma/cli-engine@latest. (#30248)- The bundled
prisma-8agent skill matches the rc.9 surface again: a review of every reference file corrected 21 statements that no longer matched the CLI or runtime, and the sample projects are keyed by namespace. (#30250)
This RC tightens schema validation, adds reusable query-filter types, and fixes language-server diagnostics and PostgreSQL migration verification.
-
Text-backed enum ordering follows stored values. PostgreSQL
ORDER BYandDISTINCT ONno longer impose enum declaration order. If semantic ranking matters, use an explicit ranking expression or numeric enum values; native PostgreSQL enums retain their database ordering. Changing existing storage to numeric values requires a data-preserving migration, including defaults and constraints—not rewriting applied migration history. See the app upgrade recipe. (#30223) -
MongoDB index arguments use native schema values. Replace encoded wildcard-index
include/excludestrings with string lists and encoded text-indexweightsstrings with records. Weights must be integers from 1 to 99,999; malformed and unsupported arguments now fail validation. Thefilterargument remains quoted JSON. See the app upgrade recipe. (#29833)Before:
@@index([wildcard()], include: "[metadata, nested.path]") @@textIndex([title, body], weights: "{\"title\": 10, \"body\": 5}")
After:
@@index([wildcard()], include: ["metadata", "nested.path"]) @@textIndex([title, body], weights: { title: 10, body: 5 })
-
MongoDB rejects previously ignored attributes. Remove unsupported
@default,@updatedAt, and@db.*attributes from MongoDB schemas only. They never produced defaults or timestamps in the MongoDB contract; these remain application responsibilities. Unknown model and field attributes now fail emission, and@id/@uniquereject arguments. See the app upgrade recipe for schema migration. (#30160)Before:
status ProductStatus @default(Active) updatedAt DateTime @updatedAt
After:
status ProductStatus updatedAt DateTime
-
Reusable SQL ORM filter types require a namespace. Update
ShorthandWhereFilter,RelationPredicate,RelationPredicateInput, andRelationFilterAccessorto use<Contract, Namespace, Model>. Existing three-argument shorthand annotations must reorder their model and namespace arguments. See the app upgrade recipe and extension upgrade recipe. (#30158)Before:
ShorthandWhereFilter<Contract, 'User'>
After:
ShorthandWhereFilter<Contract, 'public', 'User'>
-
SQL ORM upsert and batch-create inputs reject nested relation callbacks.
upsert({ create }),createAll(), andcreateAndCount()no longer accept callbacks they cannot execute. Use ordinarycreate()when nested creation is intended, or create related records separately when retaining upsert or batch behavior. See the upgrade recipe. (#30144) -
Language-server support requires the schema directive. Put
// use prisma-nextbefore other non-whitespace content in each Prisma 8 schema file to retain diagnostics, completion, formatting, and other language-server features. Unmarked files are excluded from this server's schema composition. (#30140)
- Extract reusable, fully typed SQL-builder predicates with
WhereFilter<Contract, Namespace, Table>, exported from@prisma/orm-postgres/builder/types. (#30158) - PostgreSQL numeric enums now derive membership CHECK constraints for scalar and array columns. (#30223)
- Valid schemas, including those generated by
prisma orm init, no longer receive false attribute diagnostics when the language server and project interpreter load separate parser copies. Update project ORM packages to receive the fix. (#30228) - Ordering native PostgreSQL enum columns no longer fails with an
array_position(text[], enum)error. (#30191) - PostgreSQL
int8literal defaults compare correctly during migration verification when introspection returns decimal strings and the contract uses safe-integer numbers. (#30194) - Codec factories preserve their descriptor receiver, preventing codec-ID crashes from masking useful encoding and decoding errors. (#30222)
- Schema validation errors now list accepted functions, such as
now()anduuid(), instead of repeating “function call.” (#30224) - CLI help, diagnostics, telemetry notices, and generated project documentation consistently use “Prisma ORM.” (#30192)
The toolchain releases against @prisma/cli-engine@0.3.0, which now takes the Management API SDK as a peer dependency, and migration plan no longer plans silently from an empty database when migrations already exist.
The upgrade recipe for this hop: the user recipe.
- The engine peer moves to
@prisma/cli-engine@0.3.0—@prisma/orm-toolchaindeclares the unified CLI's engine as an exact peer, and this release peers 0.3.0 (up from 0.2.3). The engine's change:@prisma/management-api-sdkmoves from a regular dependency to a peer dependency (^1.55.0), supplied by theprismaCLI shell at runtime. Installs assembled by the unifiedprismaCLI resolve one engine as before; a host that pins the engine itself must move to 0.3.0 and, if it runs the engine outside the CLI shell, install the SDK itself. (prisma/prisma-cli#236)
- The
prisma-8skill, auto-installed into every project byprisma init, now teaches agents the migration system's real model — plan-from-state with explicit baselines, not a linear chain — so agents stop producing full-create plans against real databases. (#30123)
migration planrefuses to plan from an empty database when the project already has migrations on disk, instead of silently producing a full-create package that fails against any real database. A structured error explains the situation; planning from baseline remains available as an explicit opt-in. (#30122)- Structured errors'
docsUrllinks now point atdocs.prisma.io/docs/orm/v8/...instead of the pre-RCorm/next/...path. (#30126) - The language server now canonicalizes Windows file URIs, so schema files configured with Windows paths (
D:\project\next.prisma) are recognized as part of the project. (#30121) - The
devdist-tag no longer goes stale after a release: a release push tomainalso publishes a-dev.1build of the new base, so@devinstalls always resolve against the current release's engine pins. (#30125)
ORM collection pagination renames to limit/offset, and the toolchain releases against @prisma/cli-engine@0.2.3, the engine whose config loader ships the prisma init scaffold fixes from the unified CLI's rc line.
The upgrade recipe for this hop: the user recipe.
-
ORM pagination is
limit/offset, nottake/skip—.take(n)and.skip(n)are renamed to.limit(n)and.offset(n)on SQL and Mongo ORM collections, including relation refinements and grouped SQL collections; the old names are removed. Semantics are unchanged. Mongo's lower-level query builder keeps.skip(n)— it names the native$skippipeline stage, not the collection API. (#30112)Before:
await db.orm.User.orderBy((u) => u.id.asc()).skip(10).take(10).all();
After:
await db.orm.User.orderBy((u) => u.id.asc()).offset(10).limit(10).all();
-
The engine peer moves to
@prisma/cli-engine@0.2.3—@prisma/orm-toolchaindeclares the unified CLI's engine as an exact peer, and this release peers 0.2.3 (up from 0.2.2). Installs assembled by the unifiedprismaCLI resolve one engine as before; a host that pins the engine itself must move to 0.2.3. (prisma/prisma-cli#225, prisma/prisma-cli#227)
PostgreSQL temporal columns move from Date to explicit Temporal-or-text representations, the prisma-8 agent skill ships inside the ORM packages a project installs, prisma orm init hands agent-skills setup to the family-level prisma init, and the toolchain releases against @prisma/cli-engine@0.2.2 — the engine that evaluates prisma.config.ts correctly under pnpm symlink layouts.
The upgrade recipe for this hop: the user recipe.
-
PostgreSQL temporal columns read as
Temporalvalues or text, neverDate— each ofdate,timestamp(p),timestamptz(p)andtime(p)now has two representation-explicit codecs: aTemporal-backed one (the bare PSL spellingsDate,Timestamp,Timestamptz,Timeselect it) and a text one (DateString,TimestampString,TimestamptzString,TimeString). The previous codecs (pg/date@1,pg/timestamp@1,pg/timestamptz@1,pg/time@1,sql/timestamp@1/field.timestamp()) are removed with no aliases. Pick a representation per column, re-emit every contract, and provide a globalTemporalimplementation (e.g.import 'temporal-polyfill/full/global') wherever a Temporal-backed column is read. See the migration recipe. (#30073)Before:
occurredAt Timestamptz // read as DateAfter (read as
Temporal.Instant):occurredAt Timestamptz
Or, to keep PostgreSQL's text unchanged:
occurredAt TimestamptzString
-
prisma orm initno longer installs agent skills — the GitHub fetch (npx skills add) is removed and nothing replaces it insideorm init: agent-skills setup belongs to the family-levelprisma initcommand, which init's next-steps now point to. The--skip-skillsflag is removed with the behavior it opted out of, and the skill-install failure exit (code 6) is retired. Scaffolding is otherwise unchanged. (#30097) -
The engine peer moves to
@prisma/cli-engine@0.2.2—@prisma/orm-toolchaindeclares the unified CLI's engine as an exact peer, and this release peers 0.2.2 (up from 0.2.0). Installs assembled by the unifiedprismaCLI resolve one engine as before; a host that pins the engine itself must move to 0.2.2. The new engine evaluatesprisma.config.tsthrough pnpm symlink layouts that are not realpath'd (prisma/prisma-cli#222) and exports its CI detector (prisma/prisma-cli#224).
- The prisma-8 skill travels in the npm tarballs —
skills/prisma-8/ships inside@prisma/orm-postgres,@prisma/orm-sqlite, and@prisma/orm-mongo, stamped with the package name and version soprisma skills synccan copy it into agent harness directories and detect staleness from the installed packages rather than fetching from GitHub. The two upgrade skills fold into the prisma-8 router as its "upgrading" branch. (#30096)
The ORM command family now ships the unified CLI's command paths directly, the Postgres runtime survives dropped idle connections, aggregation respects the chain it terminates, and the raw lane lets an outer query reuse an inner query's typed return columns.
The upgrade recipe for this hop: the user recipe.
-
The ORM command family is keyed by the unified CLI's mount paths —
@prisma/orm-toolchain's command family now publishes the six moved commands under their unified spellings (contract format,db migrate,migration ref list|set|delete,orm init) instead of the retired standalone grammar (format,migrate,ref …,init), and every help example and error remediation names those paths (with the{bin}placeholder instead of a hardcoded binary name). Through the unifiedprismaCLI nothing moves — these were already the mounted paths — but a host that mounts the family by key, or a script driving the workspace binary with the old spellings, must respell the six commands. (#30102)Before:
prisma migrate --to production prisma ref set staging 4cb4256After:
prisma db migrate --to production prisma migration ref set staging 4cb4256
- A row-spec'd raw query exposes
.returns, a record of typed column refs, so an outer raw query can reuse an inner query's declared column (for example a CTE's aggregate) instead of restating its codec id. (#30075)
aggregate()now reduces over exactly the rows a chain'stake/skip/cursor/distinct/distinctOndescribes, instead of silently reducing over every matching row. (#30067)groupBy()now scopes pre-group pagination to the rows it groups instead of dropping it, andGroupedCollectiongainedtake/skip/orderByto page the groups themselves. (#30092)- The Postgres runtime attaches
'error'listeners to every pool and client it creates or receives, so a dropped idle connection (database restart, pooler timeout, network blip) no longer crashes the process as an uncaught exception. Pools your own code constructs and uses directly still need a listener — see the upgrade recipe. (#30081) - The PSL language server recognizes connection errors raised by any bundled copy of vscode-jsonrpc, instead of crashing when a duplicated copy raised them. (#30077)
- CLI error text interpolates the configured migrations directory instead of assuming the default path. (#30041)
orm init's failure messages no longer name retired flags or binaries (--no-skill,--force,prisma-cli init); they point at the flags that exist (--skip-skills,--confirm <directory name>) and the mountedprisma orm init. (#30083)
This release retires the prisma-next binary in favour of the unified prisma CLI, returns the default aggregates to plain JavaScript numbers with lossless variants beside them, makes CHECK constraints a declared part of the contract, and splits runtime row queries from non-returning writes. Almost every application will need to re-emit its contract and rename its config file, so read the breaking changes before upgrading.
Two upgrade recipes carry the mechanical translations for this hop: the user recipe and the extension-author recipe.
-
This repository no longer publishes a CLI; the unified
prismaCLI replaces it — nothing published ships aprisma-nextbin anymore.@prisma/orm-toolchainexposes theormcommand family at@prisma/orm-toolchain/cliand no binary, and the database facades forward no launcher. Install@prisma/cli(the prisma-cli distribution, published undernextfor the v8 line) and replaceprisma-next <command>in package scripts and CI with the unified CLI. The config file moves with it:prisma-next.config.tsis deprecated in favour ofprisma.config.ts, and the config value is now engine-shaped, with your existing ORM config nested under anormsection. Both the old filename and the flat shape still load, each printing a deprecation warning on stderr, so the rename and the rewrap can land separately. See the user recipe. (#30005)Before:
// prisma-next.config.ts import { defineConfig } from '@prisma/orm-postgres/config'; export default defineConfig({ contract: './contract.ts', output: './generated' });
After:
// prisma.config.ts import { defineConfig } from '@prisma/cli-engine'; import { defineConfig as ormConfig } from '@prisma/orm-postgres/config'; export default defineConfig({ orm: ormConfig({ contract: './contract.ts', output: './generated' }), });
-
The default aggregates are JavaScript numbers again, with lossless variants beside them —
count(),sum()over an integer column, andavg()over an integer column all returnnumber. In8.0.0-rc.1they returned abigint, abigintor decimal string depending on the column's width, and a decimal string respectively. The lossless results moved to three new operations:countBigInt()returns abigint,sumBigInt()returns abigint, andavgDecimal()returns an exact decimal string (PostgreSQL only — SQLite has no decimal type and contributes none). Acount()or integersum()whose value passes ±(2^53 − 1) now raisesRUNTIME.DECODE_FAILEDrather than returning a rounded number, so move those calls to theBigIntvariants where the magnitude is real. Unchanged:min/max,sum/avgover a float column,sumoverDecimal,sumoverUnboundedInt, and the ORM'shaving(...)operands. The SQL builder's comparison operands do move, becausefns.gt(a, b)types both sides from one codec. The same PR also makes the wide-integer codecs refuse the wrong JavaScript type: aBigIntorUnboundedIntcolumn rejects anumberand aBigIntNumbercolumn rejects abigint, withRUNTIME.ENCODE_FAILEDnaming the type that arrived, where previously a number was accepted and stringified — which let a fractional value reach an integer column unremarked. See the user recipe. (#29930)Before:
const { total } = await db.User.aggregate((a) => ({ total: a.count() })); total === 2n; // bigint const busy = await db.sql.public.user .groupBy('kind') .having((_f, fns) => fns.gt(fns.count(), 1n)); // bigint literal
After:
const { total } = await db.User.aggregate((a) => ({ total: a.count() })); total === 2; // number — countBigInt() returns the bigint const busy = await db.sql.public.user .groupBy('kind') .having((_f, fns) => fns.gt(fns.count(), 1)); // plain number literal
-
Which aggregate methods exist is now the contract's answer — the aggregate methods are no longer declared on the ORM and SQL-builder surfaces outright. Each surface is derived from the operation names in the emitted
contract.d.ts'sAggregateTypesblock, so a target or extension can contribute an operation and it appears under its own name with no client change. PostgreSQL now contributes eight operations and SQLite seven. Re-emit your contract with the CLI'scontract emit: against a contract with noAggregateTypesblock — one authored in code withdefineContract(...)and handed straight to the client, or emitted before8.0.0-rc.1— every aggregate surface resolves toAggregateOperationsUnavailable, an empty type, and each call becomes a compile error. What this release changes is compile-time only — the separate runtime guard introduced in8.0.0-rc.1still stands, rejecting an aggregate whose operation and input codec the composed target does not declare withORM.AGGREGATE_UNSUPPORTEDbefore the query runs. Separately,count(field)now rendersCOUNT(<column>)instead of accepting the argument and discarding it, so a call that got past the types — a@ts-expect-error, acount(x as never), or dynamic dispatch — now counts that field's non-null values rather than rows. See the user recipe and the extension-author recipe. (#29922) -
CHECK constraints are declared in the contract, and introspection now sees all of them — the CHECK shape in
contract.jsonchanged from{ name, column, valueSet }to{ name, prefix, expression }, whereexpressionis the raw SQL predicate andnameis a content-addressed wire name (<prefix>_<8hex>, the convention indexes and RLS policies already use). An old-shape contract is rejected on read, so re-emitting is not optional. Three consequences to plan for. Your first migration plan after upgrading drops each old unsuffixed enum constraint and adds the wire-named one, which needsdestructiveto converge. Every list (many) column gains a declared element-non-null CHECK the planner previously created without declaring. And introspection stopped parsing predicates, so hand-written constraints earlier versions could not see are now visible — and an undeclared check is an extra thatdb verify --strictreports and a destructive-capable plan drops, so read the first plan fordropCheckConstraintoperations naming constraints you wrote yourself, and declare each one you want to keep with@@check(expression: "…", map: "<physical name>"). Two API changes ride along:addCheckConstraintin committed migration files takes anexpressioninstead of acolumn/valuespair, and thetypescriptContractoptions bag now requirescreateNamespacewhenever it passesdefaultControlPolicy. AnenumType()whose codec is numeric now throwsCONTRACT.ENUM_INVALIDwhile the contract is being built rather than failing later at migrate time. See the user recipe and the extension-author recipe. (#29892)Before:
this.addCheckConstraint({ schema, table, constraint, column: 'kind', values: ['admin', 'user'] });
After:
this.addCheckConstraint({ schema, table, constraint, expression: `"kind" IN ('admin', 'user')` });
-
Runtime row queries and non-returning writes are separate calls —
query()streams rows andexecute()resolves{ affectedRows }, which is how a write now reports its affected count without a precedingSELECT. Classify each call site by the result it consumes rather than replacing everyexecute: a select, a returning write, or any plan whose rows are iterated, indexed, or decoded moves toquery, while an insert, update, or delete that returns nothing stays onexecuteand readsaffectedRows. Prepared row consumption moves fromtarget.queryPrepared(prepared, params)toprepared.query(target, params). Runtime middleware splits the same way, intobeforeQuery/interceptQuery/afterQueryandbeforeExecute/interceptExecute/afterExecutewith a sharedbeforeCompile; query interception returns{ rows }and execute interception returns{ stats }. There is no operation discriminator, compatibility alias, or generic fallback hook. On Mongo,db.querystays the static builder and the row-executingdb.executefacade method is gone — build withdb.query, then execute through(await db.runtime()).query(plan). See the user recipe. (#29921) -
rawis a reserved storage namespace — the SQL surface exposes the whole-query raw statement tag asdb.sql.raw, so a storage namespace of that name would be unreachable through the builder while the emitted types still promised its tables. Building the client now raisesORM.NAMESPACE_RESERVEDnaming the namespace. Rename it in your schema, re-emit the contract, and plan the rename against the database as you would any other namespace rename. Onlyrawis reserved. (#29997)Before:
model Event { id String @id @@schema("raw") }
After:
model Event { id String @id @@schema("ingest") }
-
Codec ids are checked where you write them — a codec id in a prepared declaration or in a contract-bound raw fragment is now checked against your contract's codec map, so an id the contract does not carry is a compile error instead of an execution-time
RUNTIME.PARAM_REF_MISSING_CODEC. The usual cause is an unversioned id. Read the correct spelling off your emittedcontract.d.ts— every id it carries now completes at both positions. A raw fragment built through a contract-free lane is unaffected, since it has no map to check against. (#30011)Before:
await db.prepare({ id: 'pg/int4' }, (sql, params) => /* … */); const upper = fns.raw`UPPER(${f.email})`.returns('pg/text');
After:
await db.prepare({ id: 'pg/int4@1' }, (sql, params) => /* … */); const upper = fns.raw`UPPER(${f.email})`.returns('pg/text@1');
-
db updatetakes consent by database name, and--yesno longer grants it — a plan that would destroy data is refused until you type the name of the connected database, and the consent binds to that exact plan by hash.--yesnever grants it; the CLI style guide has always said a blanket confirmation flag must not stand in for a destructive confirmation. Non-interactive runs grant with--confirm <database>. A dry run, or a plan with nothing destructive in it, never asks. Update any CI invocation that relied on-yto apply a destructive plan. (#29986) -
The diagnostic commands exit 4 on findings and 2 on errors —
db verify,db sign, andmigration checknow distinguish "I ran and found problems" (exit 4) from "I could not run" (exit 2). Exit 1 is reserved for a bug in the CLI itself, and exit 0 still means the check ran and found nothing.db verifyanddb signpreviously exited 1 on findings, andmigration checkexited 2. Scripts that test for any non-zero exit are unaffected; scripts that match a specific code must be updated. (#29984) -
Four
migration statusflags are retired —--graph,--all,--limit, and--refmoved to their own commands. An old invocation now gets a typedCLI.COMMAND_MOVEDerror naming the replacement rather than failing as an unknown flag. (#29982) -
Prepared statements split by their declared result —
runtime.prepare()returns one of two handles chosen from the plan the callback builds: a rows plan gives thePreparedStatementyou already have, consumed with.query(target, params), while a plan whose declared result is an affected-row count gives aPreparedExecution, consumed with.execute(target, params). This matters to extension authors: a facade that redeclaresprepare()changes its return type toPreparedForwith no logic change, and a scope that installs the prepared-query bridge must also install the execute bridge orprepared.executethrows on the bridge invariant. See the extension-author recipe. (#30006)
- Whole-query raw SQL replaces the classic
$queryRaw/$executeRawuse case. A whole statement is authored with the same tagged template the fragment mechanism already used, terminated with.returnsRow(rowSpec)for decoded, typed rows or.affectedCount()for a mutation count, and built into an ordinary query plan that flows through the existing lowering, codec, guardrail, and execution machinery — no new query lane and no new execution surface. Row-returning raw queries interpolate into other raw templates as subqueries, which gives CTEs, including data-modifying ones, for free. (#29997) @@check(expression: "…")declares a CHECK constraint in the schema, andcontract inferadopts the ones your database already has. Usename:for a wire-name prefix, so the physical constraint isname_<8hex>hashed over the predicate and compared by name — which means Postgres reprinting the expression never causes drift. Usemap:to adopt a constraint under its existing physical name, comparing the predicate byte-for-byte. Pulling a database now emits@@checkfor every live check Prisma Next did not derive, so a hand-written constraint is declared from the first pull instead of reading as an undeclared extra. (#29972)@noCheckopts a column out of the CHECK constraints Prisma Next derives for it, per kind:@noChecksuppresses all of them,@noCheck(membership)keeps the element-non-null check on a list column while dropping the membership check, and@noCheck(elementNotNull)does the reverse. The TypeScript builder equivalent is.noCheck(...).contract inferemits the attribute too, so a pulled schema passesdb verify --schema-onlyimmediately instead of needing one migration first. (#29928)- Two new column types make integer representation a per-column choice without changing the lossless
BigIntdefault.BigIntNumberreads and writes as a JavaScriptnumber, throwing outside ±(2^53 − 1) instead of rounding.UnboundedIntuses PostgreSQL unconstrainednumericstorage and round-trips integral values as exactbigintvalues at arbitrary magnitude. PostgreSQL contributes both; SQLite contributesBigIntNumber. (#29902) - The minimum supported PostgreSQL version drops from 17 to 15, the oldest version CI has been exercising all along.
initscaffolds and the--probe-dbwarning threshold follow the new floor. The reasoning is recorded in ADR 244. (#29971) - Renaming a model or column whose CHECK constraint content is unchanged now plans a single
ALTER TABLE … RENAME CONSTRAINT, classedwidening, instead of a drop plus an add. A cosmetic rename no longer needs a destructive-capable plan or a full table revalidation. (#29894) - Errors carry typed next actions. A failure that has a remedy now ships it as structured data —
nextActions, each naming a command to run — raised at the site that holds the arguments rather than spelled out in English prose a caller would have to parse. The binary name is templated at the raise site and substituted when rendered, so the suggestion stays correct as the CLI is renamed. (#29977) - CLI failures report their real error code.
envelope.codeis the stable surface consumers branch on, and a dozen failures previously reportedCONTRACT.VERIFY_FAILEDwhile hiding the true code in metadata. Every construction site now declares its code explicitly, fourteen new codes were added for the failures that had none, and the generic error path gainedcausesupport. (#29919) - Config loading reports diagnostics per section instead of throwing on the first problem it finds. A command fails only when a section it actually reads is broken, so a malformed
formattersection no longer blocksdb init. Each diagnostic is tagged with the config section and field it concerns. (#29936)
initno longer fails at its contract-emit step against the published packages. The step now runs the scaffolded project's own CLI binary as a subprocess rather than loading the new config in-process with the running CLI's bundled loader, and its failure message carries the child's stderr so a real cause is visible. The schema-path prompt also shows its default as placeholder text instead of looking blank until a keypress. (#30018)contract emitpicks the import specifier for the emittedcontract.d.tsby reading the nearestpackage.jsonabove the file it is writing, rather than falling back to the process working directory. Running the command from the wrong directory previously wrote an unresolvable internal specifier into the generated file. (#29981)- Synthesized foreign-key-backing index name prefixes are truncated to fit PostgreSQL's 63-byte identifier limit, so a mapped explicit join table no longer fails
contract emitbefore the content hash can be appended. User-authored over-budget index prefixes still fail loudly. (#30025) - A raw row spec column named
__proto__is now refused loudly instead of silently vanishing — bracket assignment onto an object literal hit the inherited setter, so the key never became an own property and the record was quietly re-parented.constructorandprototypecreate ordinary own properties and round-trip faithfully. (#30014) - The PostgreSQL direct driver no longer ends a caller's transaction. A driver-level read issued while its connection held an open transaction reported no transaction in progress, took the cursor portal-protection path, and wrapped itself in
BEGIN/COMMIT— and thatCOMMITended the caller's transaction, so later statements ran autocommit andROLLBACKundid nothing. The driver and its connection now share the transaction-open flag. (#29920) - ORM mutation reloads encode
Bytesidentities through the column codec, so a repeated upsert keyed on aBytescolumn no longer raisesORM.MUTATION_ROW_MISSING. Every unbound literal entering a select through raw collection state now becomes a typed parameter. (#29910)
- @EmaToplek made their first contribution in #29903
This is the first release on the v8 release-candidate line: releases are now versioned 8.0.0-rc.N instead of 0.x minors. It also makes every aggregate read back through the codec its target declares — count() returns a bigint — splits the SQL driver interface into a row-streaming call and a statistics call, and fixes four defects in query planning, emit, and driver error reporting.
Releases are now versioned 8.0.0-rc.1, 8.0.0-rc.2, and so on, with the counter advancing on every release. "The v8 RC" is the product name; the number underneath iterates freely, so there is no promise that the last RC before 8.0.0 final is numbered rc.1. There are no further 0.x minors. The policy is written up in docs/oss/versioning.md. (#29899)
For every package this repository publishes, latest keeps tracking the newest release, RC included. These package names have no pre-v8 stable audience to protect — a bare npm install of one of them was already an early-access install, and still is. The bare prisma package is not published from this repository; its v8 CLI shim lives in prisma/prisma-cli.
Existing installs are not moved onto the RC line by npm update. Lockfiles pin resolved versions, and a ^0.x range can never match a 8.0.0-rc.N pre-release, because pre-releases do not satisfy stable ranges. Only a fresh install, or an explicit version change on your side, lands on the RC.
Development builds move to the same line: every push to main that does not change the root version publishes 8.0.0-rc.X-dev.N under the dev dist-tag.
An RC respin may still contain breaking changes. Until 8.0.0 final ships, the pre-1.0 latitude documented in docs/oss/versioning.md carries over: a new rc.N may remove or rename APIs, change the semantics of existing ones, or change the contract format. Read the breaking-changes section of each release before you upgrade.
-
Aggregate results carry the codec their target declares — an aggregate is now read back through the codec its target declares for that result rather than through whatever the driver handed over, so aggregate application types change.
count()is abiginton both PostgreSQL and SQLite, at the top level and inside an include, and an empty relation reads0n. On PostgreSQL,sumoverint2/int4widens to abigint, whilesum(int8)andavgover any integer arenumericand read as exact decimal strings;min/maxkeep the column's own type, except overvarchar, which returnstext. On SQLite,sumover an integer column is abigintandavgis always anumber. Sweep your code for equality and arithmetic against an aggregate result (count === 2is false whencountis2n) and forJSON.stringifyover one (it throws on a bigint).having(...)operands are the exception and stay plain numbers — they are compared inside SQL and never cross a codec. Regenerate your contracts (prisma-next contract emit):contract.d.tsgains anAggregateTypesblock that both the ORM and the SQL builder resolve result types from, and against an older contract an aggregate resolves toneverin the ORM andunknownin the SQL builder. The type is not the only guard: an aggregate whose operation and input codec the composed target does not declare is rejected before the query runs, with the error codeORM.AGGREGATE_UNSUPPORTED. See the upgrade recipe and the extension-author recipe. (#29867)Before:
const rows = await posts.include('comments', (comments) => comments.count()).all(); rows[0].comments === 2; // number; 0 when the relation is empty
After:
const rows = await posts.include('comments', (comments) => comments.count()).all(); rows[0].comments === 2n; // bigint; 0n when the relation is empty
-
The SQL driver interface splits row streaming from statement statistics —
SqlQueryable(exported from@internal/sql-relational-core/ast) is now two methods wide:query()streams rows andexecute()returns{ affectedRows }. The separate prepared-execution method is gone; a prepared plan is expressed by an optionalpreparedStatementHandleon the request instead, and a driver branches on whether that property isundefined. Application code, query results, and the contract format are unaffected — this only matters if you implement or wrapSqlQueryableyourself, in which case update your implementation to the two-method shape. There is no upgrade recipe entry for this; the change is the interface itself. (#29907)Before:
interface SqlQueryable { execute<Row>(request: SqlExecuteRequest): AsyncIterable<Row>; executePrepared<Row>(request: PreparedExecuteRequest): AsyncIterable<Row>; query<Row>(sql: string, params?: readonly unknown[]): Promise<SqlQueryResult<Row>>; }
After:
interface SqlQueryable { query<Row>(request: SqlExecuteRequest): AsyncIterable<Row>; execute(request: SqlExecuteRequest): Promise<SqlStatementStats>; }
prisma-next initinstalls oneprisma-8skill instead of eleven per-workflow skills, and removes the retired skill directories from every agent's install root on each run. Each skill is now installed by name —prisma-8,prisma-next-upgrade, andprisma-8-extension-upgrade— rather than by matching a wildcard against a directory, so a new skill landing beside them is not picked up by accident. (#29853)
- A column, table, or model mapped to a name that is not a bare TypeScript identifier —
@map("has space"),@@map("data rows")— now emits a quoted property key incontract.d.tsinstead of producing a syntactically invalid file that killedcontract emit. String literals in emitted TypeScript also survive control characters and line separators, which previously produced the same failure by a different route. (#29889, #29898) - Nested
some/every/nonepredicates over a self-referential relation now keep a distinct SQL alias at every level, so an inner scope no longer shadows the parent it is supposed to correlate against. This covers one-to-one, many-to-one, one-to-many, implicit many-to-many, and explicit-junction many-to-many relations in both directions, and relations whose physical tables share a bare name across namespaces. (#29900) - Scalar reducers on a many-to-many include —
count(),sum(),avg(),min(),max()— now traverse the junction table instead of emitting a predicate against a foreign-key column that only exists on the junction, so a filtered relation count over a many-to-many relation returns the right number. (#29888) - A failed retry of a stale PostgreSQL prepared statement now surfaces a structured error envelope with the code
DRIVER.PREPARE_FAILED, carrying the normalized driver error as its cause, instead of an unlabelled failure. (#29907)
This is the namespace release: Prisma Next now publishes as 17 packages under the @prisma scope, and an application depends on exactly one database facade. It also completes the structured error-code scheme across every plane, makes relation-loading lossless for big numbers and temporal values, and gives every SQL index and RLS policy an exact, migratable name.
-
One
@prismapackage per application — the@prisma-next/*scope is retired; nothing publishes under it again. An application depends on exactly one database facade —@prisma/orm-postgres,@prisma/orm-sqlite, or@prisma/orm-mongo— plus any extension packs it uses (now named@prisma/orm-extension-*); everything else arrives as the facade's exact-pinned dependencies. Regenerating your contract rewrites generated imports to facade entrypoints with nocontractHashchange. See the 0.16-to-0.17 upgrade recipe and the extension-author recipe. (#29864, #29880, #29883, #29884)Before:
After:
"dependencies": { "@prisma/orm-postgres": "0.17.0" }
-
Every published error is a structured envelope with a dotted code — the four legacy error systems (
PN-CLI-4001-style codes,RUNTIME.DECODE_FAILED-style codes, and codeless error classes) consolidate into one scheme: a structural envelope carrying aNAMESPACE.SUBCODEcode, recognized by theisStructuredErrortype predicate instead ofinstanceof. The ORM, contract-authoring, adapter/target, extension, and framework planes are all swept; legacy error classes (PslFormatError, the Supabase and SQL-escape classes, framework classes) are deleted. Prisma 7'sP1001-style codes are not carried over. (#1016, #1021, #1025, #1049, #1053, #1063)Before:
if (error instanceof PslFormatError) { report(error.diagnostics); }
After:
if (isStructuredError(error) && error.code === 'PSL.PARSE_FAILED') { report(error.meta.diagnostics); }
-
Content hashes are bare hex — the
sha256:prefix is gone from every surface (emitted contracts, migration manifests, refs, CLI output, and the database marker), and loaders reject the prefixed form. Contract hash values are unchanged;migrationHashvalues change. A codemod in the 0.16-to-0.17 recipe converts checked-in migration trees. (#1033) -
Migration contract snapshots move into a content-addressed store — per-migration sibling snapshot files and ref-paired copies are replaced by a single
migrations/snapshots/<hex>/store per migrations root; every distinct contract is stored once, andmigration.tsimports its bookend contracts from the store. This is a clean break with no fallback reader; a one-shot migrator (scripts/migrate-migrations-layout.mjs) converts existing trees and re-verifies everymigrationHashunchanged. (#1018, #1024) -
PostgreSQL native types are authored in type position; the
@db.*attribute channel is removed — write the native type directly (VarChar(255),Uuid,Timestamptz) instead of a base type plus@db.*attribute; remaining@db.X(args)usage fails with the exact replacement spelled out.Jsonre-binds to nativejsonstorage, with a newJsonbscalar for jsonb (what every pre-0.16Jsonfield meant — switch those fields to keep a byte-identical contract), andDatere-binds to the correctpg/date@1codec. (#1022, #1036, #1054)Before:
model User { id String @id @db.Uuid name String @db.VarChar(255) }
After:
model User { id Uuid @id name VarChar(255) }
-
Relation-loading and aggregates are lossless — values read through
.include()no longer pass through lossy JSON: every codec gains an explicit lossless JSON form produced inside the database. 64-bit integers arrive asbigintinstead of silently rounding, decimals as exact strings, and temporal columns decode correctly. Aggregate result types change accordingly:count()is abigint, decimal sums are strings. Regenerate your contract after upgrading. (#29844, #1023, #1051) -
SQL indexes and RLS policies are name-identified — every index and RLS policy carries an exact name in the contract, names travel on the wire, live objects can be adopted by exact name (
@@map), and a rename converges by renaming instead of drop-and-recreate. (#1047, #29807, #29865) -
extensionPacksconfig key renamed toextensions— inprisma-next.config.ts, the TS builder, client options, and the emitted contract's top-level key. The old key fails loudly. Because the key sits in the hashed contract bytes, all contract hashes change: re-emit and re-anchor migrations per the recipe. Two smaller key renames ride along:contract.source.sourceFormat→format, and the facadedefineConfigoptionoutputPath→output. (#1032) -
Count-only mutation terminals renamed —
createCount(...)/updateCount(...)/deleteCount()becomecreateAndCount(...)/updateAndCount(...)/deleteAndCount(); behavior andPromise<number>results are unchanged, with no compatibility aliases. (#1044)
- Expression, partial, and unique indexes are authorable in both PSL and the TypeScript builder. (#1048)
contract inferreaches full fidelity — indexes, policy blocks, and@@rlsare captured — and signs the database, so introspect-then-verify works end to end on an adopted database. It also infers 1:1 relations from unique indexes. (#29808, #1038)- Every error code is documented on an in-repo reference page (221 codes), kept complete by a CI check, and error envelopes carry a
docsUrlpointing at their per-code anchor. (#1027, #29806)
- MongoDB write results decode through their type codecs instead of returning raw wire values. (#29879)
- The Postgres runtime driver serializes queries per pinned client, fixing interleaved-query failures on a shared connection. (#29839)
- Mixed-case native-enum casts are quoted, so PascalCase enum type names survive Postgres case-folding. (#1034)
- Driver cursor streaming runs inside an explicit transaction, fixing dropped-portal failures under load. (#1017)
- Published type declarations name only dependencies a consumer will actually have installed. (#29862)
This release makes contract infer output round-trip cleanly through contract emit, materializes foreign keys and indexes as discrete contract entities, fixes the first-run experience of the Supabase extension end to end, and adds per-codec temporal presets that spell column type and auto-update behavior together.
-
Foreign keys and indexes are discrete contract entities —
contract emitnow materializes each foreign key'sconstraint/indexauthoring booleans into separate persisted entities: aforeignKeys[]entry is the referential constraint only, and every backing index (including one backing a foreign key) is its own namedindexes[]entry. The authoring surface is unchanged (@relation(index:), TSfk({ constraint, index }),foreignKeyDefaults), and re-runningcontract emitregenerates the new shape with no source change. TypeScript that read.constraint/.indexoff a contract'sforeignKeys[]entry must read the discreteindexes[]entry instead. No migration or DDL change — the schema the planner anddb verifyderive is identical. See the 0.15-to-0.16 upgrade recipe and the extension-author recipe. (#989)Before:
"foreignKeys": [ { "source": { "columns": ["user_id"] }, ..., "constraint": true, "index": true } ], "indexes": []
After:
"foreignKeys": [ { "source": { "columns": ["user_id"] }, ... } ], "indexes": [ { "columns": ["user_id"], "name": "identities_user_id_idx" } ]
-
A singular back-relation over a non-unique foreign key is rejected at emit — a schema declaring a 1:1 relation whose foreign-key columns are not covered by a unique constraint previously emitted a contract claiming a guarantee the database cannot enforce. Emit now fails with
PSL_NON_UNIQUE_BACKRELATION; add@unique/@@uniqueto the foreign-key fields, or make the back-relation field a list. (#1015)Before (accepted, emitted
cardinality: '1:1'):model Profile { id Int @id userId Int // no @unique user User @relation(fields: [userId], references: [id]) } model User { id Int @id profile Profile? }
After: the same schema fails emit with
PSL_NON_UNIQUE_BACKRELATION. Add@uniquetouserId(or makeprofileaProfile[]list). -
contract inferdeclares identity-column defaults, anddb verify --strictnow sees them — infer emits@default(autoincrement())for a PostgresGENERATED ... AS IDENTITYcolumn (previously nothing), anddb verifyintrospecting a live identity column resolves its default toautoincrement()too. If you rundb verify --strictagainst a table with an identity column whose contract predates this fix, verify reports that default as an unexpected extra — re-runcontract inferfor the affected table, or add@default(autoincrement())by hand. Without--strict, nothing changes. (#1011) -
contract inferback-relation names no longer double-pluralize — the hand-rolled pluralization rule turned already-plural table names intosessionses; infer now uses real inflection (sessionsstayssessions,statusstill becomesstatuses). Already-generated.prismafiles are untouched, but the nextcontract inferrun against a database with already-plural table names renames the affected back-relation fields — public field names your code reaches via.include()/.select()and the generated types — so diff the regenerated file and update call sites. (#1011) -
@prisma-next/extension-supabaseno longer exports./test/utils— thebootstrapSupabaseShimsubpath typechecked but never worked from npm (it reads fixture files that were never published, so every call failed with ENOENT). Delete the import and any test setup that called it. (#997)
-
Per-codec temporal presets carry execution-default behavior as arguments, so a column's exact type and its auto-update behavior can finally be spelled together — e.g. the
timestamp(3)columns Prisma ORM migrations generate for@updatedAt.temporal.updatedAt()survives as shorthand fortemporal.timestamptz(onCreate: now, onUpdate: now). (#1003)model Page { updatedAt temporal.timestamp(3, onCreate: now, onUpdate: now) lastSeen temporal.timestamp(3) touched temporal.timestamptz(onUpdate: now) }
-
The Postgres target registers its built-in index types (
btree,hash,gin,gist,spgist,brin), so@@index(..., type: "gin")— whichcontract inferalready printed — now emits instead of throwingunregistered index type. (#1011)
- Using
@prisma-next/extension-supabaseagainst a stock Supabase project now works first-try: generated migrations for RLS contracts import, typecheck, and run (RLS operations render as methods on the migration base class);migrate's remediation for a missing extension-space migration works when followed verbatim;db verifyno longer reports phantom missing constraints;db.asUser(jwt)supports the ES256/JWKS signing current Supabase uses; anddb.asServiceRole()queries succeed with the grants a real project provides. (#997) - More
contract inferround-trip fixes: a plainDecimalfield on Postgres no longer throwsCODEC_PARAMETERIZATION_MISMATCHat connect, the 1:1 back-relation shape infer prints is accepted by emit, literal-shapeddbgenerated(...)defaults compare equal indb verifyinstead of reporting permanent drift, and foreign keys pointing outside the introspected scope are explained in infer's output instead of vanishing silently. (#1011) - MTI variant-narrowed
updateCount,deleteCount, and include-backeddeleteAllnow compile their predicates through a correlated subquery that joins the variant table — previously the generated SQL could reference the variant table without it being in scope. (#940) - An explicit
.select(...)on a polymorphic query or include now restricts MTI variant fields to the selection; unselected variant fields no longer leak into results. Omitting the selection keeps the full default shape. (#984) - The language server surfaces config-load failures as a diagnostic on the config file (
PRISMA_NEXT_CONFIG_LOAD_FAILED) and keeps serving schema diagnostics from the last working configuration when a reload fails, instead of silently wiping every marker. (#974) - Migration operations are now ordered by a dependency graph over schema-diff issues instead of a hand-maintained per-kind integer table, fixing drop-order defects (e.g. a column dropping before its own constraint). For code reading the migration-diff internals:
SchemaDiffIssue.reasonis removed — discriminate via the presence ofexpected/actual, or theissueOutcomehelper from@prisma-next/framework-components/control. (#992)
This release ships Postgres row-level security end-to-end (policies for every operation, explicit @@rls enablement, role declarations — authored in PSL or TypeScript, planned by migration plan, drift caught by db verify), native Postgres enums (external adoption and a managed lifecycle), the complete introspected Supabase contract, a PSL language server (prisma-next lsp) with formatting, completions, and semantic highlighting, native scalar-list columns, PSL many-to-many authoring, and one unified schema differ behind db verify and migration planning. SQL ORM includes now decode through codecs, matching top-level reads.
-
SQL ORM includes decode through codecs — every scalar field of an included relation now decodes through its contract-bound codec, matching top-level query results. Code that relied on included fields keeping the database's raw JSON representation must be updated: Postgres
byteainclude fields returnUint8Arrayinstead of\x-prefixed hex text, timestamp fields returnDateinstead of strings, and custom codec-backed fields return whatever the codec'sdecodeJsonproduces. Custom SQL codec authors:encodeJson/decodeJsonnow use the exact scalar shape the database produces inside JSON values — see the extension-author recipe for the built-in representation changes. (#942)Before:
const [post] = await db.orm.public.Post.find({ include: { author: true } }); post.author.avatar; // '\\x89504e…' (raw hex text) post.author.createdAt; // '2026-07-01T12:00:00' (string)
After:
post.author.avatar; // Uint8Array post.author.createdAt; // Date
-
db verify --jsonreports a singleschema.issueslist — the splitschema.issues/schema.schemaDiffIssuespair collapses into oneschema.issuesarray of{ path, reason, message, expected?, actual? }, and the retiredoutcomefield is replaced byreason('missing'→'not-found','extra'→'not-expected','mismatch'→'not-equal'). The same collapse applies toschema.warnings. Update scripts or CI steps that readschemaDiffIssuesor compare.outcome. See the 0.14→0.15 upgrade recipe. (#921)Before:
{ "schema": { "issues": [], "schemaDiffIssues": [{ "outcome": "missing", "message": "…" }] } }After:
{ "schema": { "issues": [{ "reason": "not-found", "path": ["…"], "message": "…" }] } } -
RLS policies require
@@rlson the target model — RLS enablement is an explicit, authored table attribute. Apolicy_*block'stargetmodel must declare@@rls, orcontract emitfails withPSL_EXTENSION_TARGET_MODEL_MISSING_ATTRIBUTE. Plan semantics follow the marker: a marked table with RLS off plansENABLE ROW LEVEL SECURITY, removing every policy keeps RLS enabled (fail-closed deny-all), and removing@@rlsplansDISABLE ROW LEVEL SECURITY(requires the destructive allowance). Renaming only a policy's name plans a singleALTER POLICY … RENAME TOinstead of drop+create. Extension authors constructingPostgresTableSchemaNodeby hand must supply the now-requiredrlsEnabledboolean. (#945)Before:
model Profile { id Uuid @id userId Uuid @unique }
After:
model Profile { id Uuid @id userId Uuid @unique @@rls }
-
Extension authors: SQL contract authoring requires a target
createNamespace— the SQL family no longer materialises a placeholder namespace, soprismaContract(...)/defineContract(...)from@prisma-next/sql-contract-psl/@prisma-next/sql-contract-tsneed the target's namespace factory (postgresCreateNamespace/sqliteCreateNamespace); target-packdefineContractwrappers already supply it, so app authors are unaffected.SqlNamespaceis now an abstract class;buildSqlNamespace,buildSqlNamespaceMap,SqlBoundNamespace, andSqlUnboundNamespaceare removed, and hand-written namespace literals carry the targetkind(e.g.'postgres-schema') instead of'sql-namespace'. See the extension-author recipe. (#864) -
Extension authors: the coordinate-based schema-diff SPI is retired — the migration planner and
db verifynow run on one generic node differ.collectSqlSchemaIssues/collectSqlSchemaIssuesPerNamespace,diffPostgresDatabaseSchema, andSqlControlTargetDescriptor.diffDatabaseSchemaare removed (usediffSchemasor a target'sbuildXPlanDiff);MigrationPlanner.plan()'skeepDiffIssuepredicate is replaced by anownershiporacle; the issue typesBaseSchemaIssue/SchemaIssue/EnumValuesChangedIssueare gone —SchemaDiffIssueis the single issue shape everywhere, including the codecverifyTypehook; andgraphWalkStrategyis renamedresolveRecordedPathin@prisma-next/migration-tools/aggregate. See the extension-author recipe. (#921, #894) -
Extension authors: restricted-column typing goes through the codec — a column restricted to a value set derives its TS literal union by rendering each stored value through the codec's
renderValueLiteral(value, side), replacing the framework's deleted domain-enum override. Custom codec descriptors used by enum/restricted columns must implement it, or the column widens to the codec's output type. (#896) -
Extension authors: Mongo
deriveJsonSchemasources enums from value sets — the fourth argument ofderiveJsonSchema/derivePolymorphicJsonSchemachanges from a domain-enum map to a value-set map (contract.storage.namespaces[<ns>].entries.valueSet). Callers throughmongoContract(...)/defineContract(...)need no change. (#900) -
Extension authors:
ScalarFieldState's first generic is the column descriptor —ScalarFieldState<'pg/text@1', …>becomesScalarFieldState<ColumnTypeDescriptor<'pg/text@1'>, …>, so field states preserve the whole descriptor type (including native-enum member tuples). Built contract types also keep literalnativeType/typeParamsinstead of widening tostring. (#958) -
Extension authors:
native_enumentities serialize intocontract.json, keyed by physical type name — packs declaring native Postgres enums must re-emit their bundled contract so theentries.native_enummaps land in the published artifacts (this is what lets a consumer'scontract infersubtract pack-owned enum types). Code addressing an entry by key switches from the PascalCase name to the physical Postgres type name (entries.native_enum.aal_level, not.AalLevel). (#946, #954)
-
Postgres row-level security, end-to-end — PSL gains
policy_select,policy_insert,policy_update,policy_delete, andpolicy_allblocks (withusing/withCheckpredicates and per-role targeting), the@@rlsenablement attribute, and standaloneroledeclarations insidenamespace unbound { }.migration planplans the full lifecycle (ENABLE/DISABLE ROW LEVEL SECURITY, policy create/drop, rename viaALTER POLICY), anddb verifyfails on policy drift and on declared roles the live cluster lacks. The same surface is authorable in the TypeScript DSL (policySelect(...),rlsEnabled(model),role(name)), producing wire-name-identical contracts. (#771, #868, #945, #950, #957, #959) -
Native Postgres enums —
CREATE TYPE … AS ENUMtypes are first-class again, this time as explicit entities. External types the database already owns (e.g. Supabase'sauth.aal_level) are declared vianative_enumblocks, typed as member-value literal unions, adopted bycontract infer, and read at runtime through the new Postgres-onlydb.nativeEnumsaccessor. Managed native enums get a migration lifecycle: create/delete, and member addition viaALTER TYPE … ADD VALUE(other member changes are refused with a converting-migration hint). Also authorable in the TypeScript DSL vianativeEnum(name, ...values)+field.column(pg.enum(handle)), with the member union visible intypeof contractwithout an emit. (#906, #944, #949, #970, #935, #958) -
The complete Supabase contract —
@prisma-next/extension-supabasenow ships the full introspected description of everything Supabase owns: everyauthandstoragetable, all native enum types, and the three platform roles (anon,authenticated,service_role), up from the previous 5-table minimum. A secondarydb.asServiceRole().supabase.{sql,orm}admin root reads Supabase-internal tables asservice_role, and the extension ships with docs, a real-Supabase acceptance harness, and a user-facingprisma-next-supabaseskill. (#845, #960, #985, #987) -
PSL language server — a new
prisma-next lspsubcommand serves diagnostics, formatting, completions (types and block templates), semantic highlighting, folding regions, and symbol-table diagnostics over LSP, backed by the fault-tolerant CST parser (which now fully replaces the legacy parser).prisma formatformats PSL from the CLI, and a browser playground wires a Monaco editor to the language server. (#852, #851, #850, #857, #862, #871, #878, #869, #856, #887, #972) -
PSL native scalar lists — scalar-list fields (
String[],Int[], …) lower to native array storage columns instead of a JSONB fallback, end-to-end: author, migrate, and infer, gated on the adapter-reportedscalarListcapability. (#870, #846) -
PSL authors many-to-many — an
N:Mrelation with athroughjunction is now authorable in PSL, completing the M:N surface whose read side landed in 0.14. (#819) -
Per-migration contract snapshots — each applied migration persists its contract snapshot in a 1:1 ledger companion table, and the
Migrationbase class takes typed start/end contract JSON, exposingthis.startContract/this.endContractviews for data-transform migrations. (#908, #879) -
Client-safe static surface — new
@prisma-next/{postgres,sqlite,mongo}/staticentrypoints export<target>Static({ contractJson }), a driver-freeExecutionContextplus derivedenums, query builder,raw, andcontract— safe to import in client bundles. The runtime facades also exposedb.contextanddb.contract. (#888) -
Mongo enums, end-to-end — enums are authorable for MongoDB in PSL and the TypeScript builder, enforced at the database layer via a planner-generated
$jsonSchemavalidator, and typed from a stored value set the same way SQL enums are. The Mongo client also gainsdb.rawanddb.execute(plan). (#834, #900, #880) -
Extension-aware
contract infer—contract inferomits database elements a stack extension pack's contract already describes, and resolves a foreign key into pack-owned space as a qualified cross-space relation (e.g.supabase:auth.AuthUser) instead of re-declaring the pack's tables. (#919) -
Variant-declared relations in the ORM — the
.variant('X')-narrowed accessor surfaces relations the variant model declares (filterable and includable), alongside the base model's relations. (#933, #976) -
Enum
@@typeinference — a PSLenumblock may omit@@type; the codec is inferred from the member values (text for string members, int for integers). (#905) -
@relation(index: false)andinetcolumns — PSL's@relationgains an optionalindexargument for foreign keys whose columns genuinely have no backing index (contract inferemits it automatically), and the Postgres target gains apg/inet@1codec soinetcolumns are authorable asString @db.Inetand inferrable. (#960)
-
@default(false)survives emission — the contract canonicalizer no longer stripsvalue: falsefrom resolved defaults, so a boolean-falsecolumn default is present in the emittedcontract.jsonand round-trips against live introspection. Re-emitting an affected contract changes its storage hash. (#904) -
Mongo reshaping reads decode through codecs — aggregation reads through
$project/$addFieldsstages decode their output fields instead of returning raw BSON (a projected_idnow comes back decoded, not as a rawObjectId). (#897) -
pgbindings resolve by structure — a caller-supplied Pool/Client from a duplicatedpgcopy in a bundle now resolves correctly instead of throwingUnable to determine pg binding typeat boot; newisPgPool/isPgClientguards are exported from@prisma-next/postgres/runtime. (#969) -
Array columns verify cleanly — a scalar-list column's derived schema IR keeps the bare element type with
many: true(previously every list column verifiednot-equalagainst live introspection); Postgres introspection also excludes expression-keyed indexes and no longer collides unique and non-unique indexes over identical columns. (#960) -
Stack-missing migration errors name the failing operation — the error raised when a migration references an operation the stack doesn't provide now says which operation. (#953)
This release reshapes the enum surface (PSL enum is now a domain concept backed by a value-set CHECK constraint, not a native Postgres type), makes the SQL builder always-qualified by namespace, adds native UUID storage on Postgres, ships a new fault-tolerant PSL parser, completes the read side of many-to-many (correlated includes plus some / every / none filters through the junction), and adds a Supabase façade alongside several runtime-class renamings. Most breaking changes have a matching codemod or upgrade recipe.
-
PSL
enumbecomes the domain enum — anenumblock now authors a text-class column whose value set is enforced by a CHECK constraint, not a nativeCREATE TYPE … AS ENUM. Each block must declare@@type("<codec-id>")(typicallypg/text@1) and map members to database values withName = "value". The transitionalenum2keyword is retired (rename toenum— emitted contract is identical). Native enum machinery is deleted:enumType(name, values[])/enumColumnfrom@prisma-next/adapter-postgres/column-types, thepg/enum@1codec, and adoption of native enum types incontract inferare all gone. Databases carrying a native enum type need a one-time converting migration (ALTER column totextUSING::text, add the value-set CHECK,DROP TYPE) —contract inferrefuses native enum types and names them. See the 0.13→0.14 upgrade recipe and the extension-author recipe. (#817)Before:
enum user_type { admin user }
After:
enum user_type { @@type("pg/text@1") admin = "admin" user = "user" }
-
Query builder and ORM are always qualified by namespace — the flat by-bare-name accessors are removed at the builder layer; the Postgres facade exposes the namespaced surface. On Postgres,
db.sql.<table>becomesdb.sql.<namespace>.<table>anddb.orm.<Model>becomesdb.orm.<namespace>.<Model>(publicfor a standard single-schema project). Direct builder calls (sql.<table>,orm.<Model>) migrate the same way. SQLite and Mongo are unaffected — their single-namespace facade keeps the flat surface working. No codemod: the correct namespace is the one each table/model is declared in. The generatedcontract.d.tsalso drops the flat top-levelexport type Models— read models per-namespace asContract['domain']['namespaces']['<namespace>']['models']and re-emit. See the 0.13→0.14 upgrade recipe. (#778)Before:
const users = await db.sql.user.select('id', 'email').build().execute(); const alice = await db.orm.User.find({ where: { id } });
After:
const users = await db.sql.public.user.select('id', 'email').build().execute(); const alice = await db.orm.public.User.find({ where: { id } });
-
UUID field presets renamed by storage encoding —
field.uuid()→field.uuidString(),field.id.uuidv4()→field.id.uuidv4String(),field.id.uuidv7()→field.id.uuidv7String(). The new names describe thechar(36)storage encoding (the emitted codec,sql/char@1, is unchanged). Postgres-nativeuuidcolumns use the newfield.uuidNative()/field.id.uuidv4Native()/field.id.uuidv7Native()presets from@prisma-next/postgres/contract-builder. The rename is mechanical — a colocated codemod ships in the 0.13→0.14 upgrade recipe. (#810)Before:
id: field.id.uuidv7(), externalId: field.uuid(),
After:
id: field.id.uuidv7String(), externalId: field.uuidString(),
-
Postgres migration op factories become methods on
Migration— the bare op factory functions previously exported from@prisma-next/postgres/migration(and the@prisma-next/target-postgres/migrationalias) are removed. Each is now a protected method on thePostgresMigrationbase class — call it asthis.<op>(...). Positional arguments are replaced by a single options object. A codemod ships in the 0.13→0.14 upgrade recipe. (#813)Before:
import { addForeignKey, dropColumn } from '@prisma-next/postgres/migration'; override get operations() { return [ dropColumn('public', 'user', 'legacyName'), addForeignKey('public', 'post', { name: 'post_userId_fkey', columns: ['userId'], references: { schema: 'public', table: 'user', columns: ['id'] } }), ]; }
After:
override get operations() { return [ this.dropColumn({ schema: 'public', table: 'user', column: 'legacyName' }), this.addForeignKey({ schema: 'public', table: 'post', foreignKey: { name: 'post_userId_fkey', columns: ['userId'], references: { schema: 'public', table: 'user', columns: ['id'] } } }), ]; }
-
SQL runtime class renames —
@prisma-next/sql-runtimeexportsabstract class SqlRuntimeBase(previouslySqlRuntime). The bare namesPostgresRuntimeandSqliteRuntimeare now interfaces — the types to depend on in extension and app code. The concrete classes arePostgresRuntimeImpl(from@prisma-next/postgres/runtime) andSqliteRuntimeImpl(from@prisma-next/sqlite/runtime). Code that referenced the class names to subclass them switches to theImplnames. Code using the facade factories (postgres(...),sqlite(...)) is unaffected. (#806) -
createRuntimeremoved from@prisma-next/sql-runtime— use the target facade factory (postgres(...)/sqlite(...)) or construct the target class directly (new PostgresRuntimeImpl({...})/new SqliteRuntimeImpl({...})). The constructor options match whatcreateRuntimeaccepted, exceptstackInstanceis not taken — passadapterdirectly. App code using the facade factories is unaffected. (#806) -
SqlContractSerializerno longer accepts Postgres contracts — the family serializer's entries registry only knows SQL-family built-ins (table,valueSet) and rejects the Postgres-specifictypekey that every Postgres namespace carries. Migration files and app code that deserialize a Postgres-emitted contract must usePostgresContractSerializerfrom@prisma-next/target-postgres/runtime. SQLite and family-only contracts are unaffected. (#812)Before:
import { SqlContractSerializer } from '@prisma-next/family-sql/ir'; const contract = new SqlContractSerializer().deserializeContract(json) as Contract;
After:
import { PostgresContractSerializer } from '@prisma-next/target-postgres/runtime'; const contract = new PostgresContractSerializer().deserializeContract(json) as Contract;
-
Extension authors:
SqlNamespace.entriesis an open dictionary — the closed shape ({ table?, valueSet? }) is gone.entriesis nowReadonly<Record<string, Readonly<Record<string, unknown>>>>, so dot-access like.entries.tableno longer compiles. Read tables via thenamespaceTables(ns)helper from@prisma-next/sql-contract/types, or via bracket notationentries['table']; the concrete class instances still expose typed getters (ns.table). See the extension-author recipe. (#812)
-
Postgres-native UUID storage —
field.uuidNative()/field.id.uuidv4Native()/field.id.uuidv7Native()from@prisma-next/postgres/contract-builderauthor columns backed by the nativeuuidtype. The cross-target*String()presets continue to emitchar(36). (#810) -
Many-to-many reads land —
N:Mrelations through athroughjunction can now be eagerly loaded viainclude()(correlated reads, slice 1) and filtered withsome/every/nonethrough the junction (slice 2). M:N validation arrived in 0.13; the runtime read surface is wired up in this release. (#679, #680) -
Supabase façade —
@prisma-next/extension-supabaseships asupabase()façade andSupabaseRuntimethat composes the cross-contract foreign keys introduced in 0.13 into a runnable extension. (#792) -
Fault-tolerant PSL parser — a new recursive-descent parser produces a full syntax tree (
SourceFile) even when the input contains errors, so editor integrations can report diagnostics and surface partial structure without bailing on the first failure. (#795) -
Custom and parameterized codecs in control-path queries — adapters now honor custom and parameterized codecs when encoding values on the control path (catalog reads, schema-verification queries, migration-state lookups), matching how user-data queries already handled them. (#807)
-
contract inferwrites apragmaheader — inferred PSL contracts now carry apragmablock recording the inference source and options, so re-running infer or auditing a generated schema is unambiguous. (#801) -
Per-namespace typed resolution in the builder — the emitted
contract.d.tsTypeMaps nest by namespace, so the query builder and ORM client resolve each namespace's own columns and fields — fixing same-bare-name models declared in more than one namespace. Re-emit picks up the new shape. (#803) -
Enum input types are exhaustively typed in the emitted
.d.ts— an enum-restricted field's input type renders as the literal member union (matching the output side), so create/update calls are exhaustiveness-checked at compile time. Re-emit picks up the new shape. (#797) -
Typed
db.enums.<namespace>.<Name>accessor — the emitter generates adomainblock incontract.d.tsthat exposes each PSL-authored enum as a literal-typedContractEnumAccessor(values,names,members).contract.jsonis unchanged; re-emit picks up the new types. (#809) -
Enum member defaults via
@default(EnumType.Member)— the PSL interpreter and contract-ts authoring surface resolve a member default to the corresponding database value literal. (#808)
-
sql-orm-clientmodel accessors typed by selected variant — accessing a model on the ORM client narrows the result type to the selected variant rather than the union of all variants. (#790) -
Emitter emits enum input literals — fixes a hole where enum-restricted input types fell back to the codec's broad input type instead of the literal member union. (#797)
-
Un-namespaced Postgres models default to
public— un-namespaced models in a Postgres contract correctly default to thepublicnamespace per ADR 223; the spurious empty__unbound__storage slot is gone. Re-emit picks up the shape change. (#838)
This release makes namespaces a first-class part of the query surface, adds cross-contract foreign keys to the SQL ORM, makes many-to-many a validatable contract shape, introduces a per-object control policy (@@control) that decides what Prisma manages, ships domain enums backed by storage value-sets, and gives the migration CLI a unified graph-tree view across list / log / status / show. Telemetry also flips from opt-in to opt-out. A few changes require a one-time contract re-emit — all are covered by the linked upgrade recipes.
-
Telemetry is now opt-out — anonymous CLI telemetry is collected by default and you opt out, where previously you opted in. Set
PRISMA_NEXT_DISABLE_TELEMETRY=1(orDO_NOT_TRACK=1) to turn it off. Seedocs/Telemetry.mdfor what is collected and every opt-out signal. (#676) -
MTI variant tables materialize a base-PK link column — a PSL
@@base(Parent, "tag")variant that carries its own@@map(and is therefore stored in its own table) now emits a base-PK link column in storage: the variant table gains a copy of the base table's primary-key column(s), a primary key over them, and a cascading foreign key (ON DELETE CASCADE) referencing the base table's primary key. Previously the variant table held only the variant-specific columns with no primary key and no link to its base. This changes the emittedcontract.json/contract.d.tsand the contract'sstorageHash. Re-emit your contract, then plan and apply the matching migration. Variants that share the base table (no own@@map) are unaffected. See the 0.12→0.13 upgrade recipe. (#669)Before (emitted
contract.json, variant tablebug):"bug": { "columns": { "severity": { "codecId": "pg/text@1", "nullable": false } } }
After:
"bug": { "columns": { "id": { "codecId": "sql/char@1", "nullable": false }, "severity": { "codecId": "pg/text@1", "nullable": false } }, "primaryKey": { "columns": ["id"] }, "foreignKeys": [ { "name": "bug_id_fkey", "columns": ["id"], "references": { "table": "task", "columns": ["id"] }, "onDelete": "cascade" } ] }
-
Contract storage IR moved to a namespace envelope — the SQL/Mongo storage IR is now keyed by namespace (
storage.namespaces.<ns>.entries.<kind>), and cross-references are explicit{ namespace, model }objects indomain. Consumer impact is mechanical: re-emit withprisma-next contract emitto pick up the new shape. No codemod or source change is required, but the contract'sstorageHashchanges, so plan and apply a migration afterward. (#715) -
Extension authors: codec-resolution SPI takes a leading
namespaceId—CodecDescriptorRegistry.codecRefForColumn(table, column)is nowcodecRefForColumn(namespaceId, table, column), and the freecodecRefForStorageColumn(storage, table, column)is nowcodecRefForStorageColumn(storage, namespaceId, table, column)(both in@prisma-next/sql-relational-core). Thread the namespace the table lives in through every call site that stampscodeconto AST nodes. There is no codemod — the right namespace is call-site-specific. See the 0.12→0.13 extension-author recipe. (#715)Before:
const ref = descriptors.codecRefForColumn('document', 'embedding');
After:
const ref = descriptors.codecRefForColumn('public', 'document', 'embedding');
-
Extension authors: empty
typeParamsstripped fromstorage.types— the canonicalizer now omitstypeParamsfromstorage.typesentries when it is an empty object (e.g. atypes { Uuid = String @db.Uuid }named-type alias). Runtime behaviour is unchanged, but the emittedcontract.jsonand itsstorageHashdiffer. If your extension shipped acontract.jsonwith"typeParams": {}, re-emit and re-pin your migration baselines. See the 0.12→0.13 extension-author recipe. (#753)
-
Namespace-aware DSL/ORM surface — the typed query and ORM surface now exposes namespaced accessors so models in different namespaces are addressed explicitly and two same-named tables in different namespaces no longer collide. Additive — existing single-namespace code is unchanged. (#720)
-
Many-to-many is now a validatable contract shape —
N:Mrelations carrying athroughjunction descriptor are now a first-class, validatable part of the contract (they previously failed validation). The ORM runtime surface for M:N —.include()across the junction,some/every/nonefilters, and junction writes — is not wired up yet and lands in a follow-up release; nested M:N mutations currently throw. (#669, #678) -
Cross-contract foreign keys — a relation field can reference a model owned by another contract space (e.g.
supabase:auth.AuthUser), with named-type aliases (types { Uuid = String @db.Uuid }) for database-native column types. The planner and verifier resolve the cross-space reference and emit the foreign key, including cascading deletes. See the 0.12→0.13 upgrade recipe for the authoring pattern. (#745, #752, #756, #765)types { Uuid = String @db.Uuid } namespace public { model Profile { id String @id @default(uuid()) username String userId Uuid @unique user supabase:auth.AuthUser @relation(fields: [userId], references: [id], onDelete: Cascade) @@map("profile") } }
-
Per-object control policy (
@@control) — a model or other contract object can declare whether Prisma manages its schema, and a contract can set adefaultControlPolicy. Migration DDL generation and schema verification react to each object's policy, so you can keep externally-owned objects out of Prisma's managed surface. (#717, #711) -
Domain enums with storage value-sets — enums are now a domain concept backed by storage value-sets. On Postgres,
enumblocks lower to a native enum type (CREATE TYPE … AS ENUM); SQL targets without native enum support approximate the allowed values with check constraints. (#750, #755) -
Unified migration graph view in the CLI —
migration list,log,status, andshownow render the migration history as a consistent graph tree with colored lanes, a--legend, and one schema-locked--jsonshape across the read commands.migrate --showpreviews the migration path read-only before you apply it. (#706, #704, #705, #735, #741, #767) -
Readable per-migration ledger — the migration apply ledger is now a per-migration journal, read back as one flat chronological table by
migration log. (#665, #704) -
db.transaction()on the SQLite facade —@prisma-next/sqlitegains a facade-level transaction API (db.transaction(async (tx) => …)), mirroring the Postgres facade. (#737) -
Declarative SPI for extension-contributed PSL blocks — extensions can declare top-level PSL blocks declaratively, and
contract inferround-trips them through a generic PSL printer. (#753, #754, #757) -
@prisma-next/extension-supabase— a new extension package and anexamples/supabasewalking skeleton that wires a cross-contract foreign key from an app model to Supabase'sauthschema. (#746, #765) -
STI variants can declare their own fields — a PSL
@@base(Parent, "tag")variant with no own@@map(single-table inheritance) may now declare its own scalar fields. Each is materialized as a (nullable) column on the shared base table, and the variant no longer emits a stray shadow table. Previously such a contract failed to emit withreferences non-existent column. Existing contracts re-emit identically. (#669) -
Backward cursor pagination —
OrderByItem.reverse()flips an order-by direction for fetching the previous page. (#671) -
Postgres JSON defaults emit a
::jsonb/::jsoncast — JSON column defaults now carry the explicit cast in generated DDL. (#763)
- Constraintless foreign keys are skipped in offline schema projection. (#744)
- Storage-sort comparison is now collation-independent. (#721)
Namespaces become first-class: un-namespaced Postgres models now live in public, the application plane is symmetric with storage, and every cross-namespace reference is explicit. This release also ratifies a version-support policy (Node 24+), simplifies runtime marker verification, closes MongoDB validators by default, and adds raw SQL to the typed builder. Several contract-shape changes require a one-time re-emit — most are mechanical and covered by the linked upgrade recipes.
-
Supported-version floors raised — the supported floor for each dependency is now the latest GA release we test against: Node.js
>=24(declared in every package'sengines), TypeScript>=5.9, PostgreSQL17, and MongoDB8.0. Bump your runtime and toolchain to meet these floors before upgrading. (#659) -
Un-namespaced Postgres models default to
public— models without an explicit namespace now emit under thepublicnamespace instead of the__unbound__sentinel (postgres-unbound-schema→postgres-schema); explicitnamespace unbound { … }still round-trips to__unbound__. Re-emit your contract socontract.json/contract.d.tspick up the new namespace key. See the 0.11→0.12 upgrade recipe. (#662)Before (emitted
contract.json):"storage": { "namespaces": { "__unbound__": { "id": "__unbound__", "kind": "postgres-unbound-schema" } } }
After:
"storage": { "namespaces": { "public": { "id": "public", "kind": "postgres-schema" } } }
-
Symmetric domain plane — models and value objects moved from flat
contract.models/contract.valueObjectstocontract.domain.namespaces.<ns>, and emittedcontract.d.tsexportsModelsviaContractModelsMap<Contract>instead ofContract['models']. Re-emit your contract; consumers reading the flat shape must adopt the namespaced helpers. See the 0.11→0.12 upgrade recipe (extension authors: the extension-author recipe also covers the removal of the@prisma-next/contract/testingsubpath — test factories now live in@repo/test-utils). (#653)Before (consuming emitted
contract.d.ts):type Models = Contract['models'];
After:
type Models = ContractModelsMap<Contract>;
-
Cross-namespace references are explicit
{ namespace, model }pairs — emitted contract roots andrelation.tonow carry an explicit{ namespace, model }object (namespace branded asNamespaceId) rather than a bare model-name string. Re-emit your contract, and update any code that readrelation.to(or a root) as a string to read.model/.namespace. (#600)Before (consuming emitted
contract.d.ts):// relation.to was a bare model-name string readonly to: 'User';
After:
// relation.to is now an explicit { namespace, model } readonly to: { readonly namespace: 'public' & NamespaceId; readonly model: 'User' };
-
capabilitiesremoved fromdefineContract— thecapabilitiesfield on the first argument ofdefineContract({ … }, …)is gone; capabilities are now contributed automatically by target components and the extension packs inextensionPacks. Delete thecapabilities: { … }block from every call site and re-emit. See the 0.11→0.12 upgrade recipe. (#574)Before:
export const contract = defineContract( { extensionPacks: { pgvector }, capabilities: { postgres: { lateral: true, jsonAgg: true } }, }, ({ field, model }) => { // … model definitions … }, );
After:
export const contract = defineContract( { extensionPacks: { pgvector } }, ({ field, model }) => { // … model definitions … }, );
-
verifyMarkerreplacesverify/RuntimeVerifyOptions— the SQL runtime'sverify: { mode, requireMarker }option is replaced byverifyMarker?: 'onFirstUse' | false(default'onFirstUse'), and the runtime no longer throws on contract-marker drift — it emits onewarn-level log line per runtime instance and proceeds. TheRuntimeVerifyOptionsexport is removed in favour ofVerifyMarkerOption. Migrateverifycall sites and switch fail-fast verification to thedb-verifyCLI. See the 0.11→0.12 upgrade recipe. (#592)Before:
const runtime = createRuntime({ stackInstance, context, driver, verify: { mode: 'onFirstUse', requireMarker: false }, });
After:
const runtime = createRuntime({ stackInstance, context, driver, // verifyMarker omitted — 'onFirstUse' is the default; pass `false` to skip });
-
Migration manifest closed;
labels/hintsremoved — the on-diskmigration.jsonschema is now closed and no longer carrieslabelsorhints; a manifest still holding either key fails to load withINVALID_MANIFEST. Both fields also leave the content-addressed migration identity, somigrationHashchanges. Run the colocated codemod to strip the keys and recompute each hash. See the 0.11→0.12 upgrade recipe. (#615) -
MongoDB emits closed
$jsonSchemavalidators by default — every emitted object schema (collection validators, nested objects, andoneOfbranches) now carriesadditionalProperties: false, and each non-variant Mongo model must resolve to anobjectId_idbefore emit succeeds. Re-emit your Mongo contracts and apply the open→closed validator change (the planner classifies it as destructive). See the 0.11→0.12 upgrade recipe. (#637) -
mongodbis now a user-supplied peer dependency —@prisma-next/driver-mongo,@prisma-next/adapter-mongo, and@prisma-next/mongono longer bundlemongodb; installmongodb@^7yourself as a peer dependency. (#597) -
.distinct(cols)now collapses to one row per group —.distinct(cols)on the SQL ORMCollection(and on nested.include(…)) now keeps a single representative row per(cols)group, matching Prisma semantics; previously it did not collapse when the projection carried other distinguishing columns. No call-site change is required, but query results change — review any logic or fixtures that relied on the old non-collapsing output. Extension authors implementingExprVisitor/ exhaustiveexpr.kindswitches must handle the newWindowFuncExprvariant — see the extension-author recipe. (#576) -
In-repo CipherStash extension removed —
@prisma-next/extension-cipherstashis no longer published from this repo; CipherStash's encrypted-field support now ships from CipherStash's own repository as@cipherstash/prisma-next. Depend on that package instead. (#650)
- Customize where the contract emitter writes via
outputPathinprisma-next.config.tsor--output-pathonprisma-next contract emit. (#584) - Raw SQL in the typed query builder (
rawSql) for Postgres and SQLite, so escape-hatch expressions compose with the rest of the builder. (#594) migration listrewritten to show the complete migration set, ref/graph context, and multi-space output instead of only the migrations along a single chain. (#603)migration graph --treerenders a condensed annotated-tree view of the migration topology. (#658)- Roll back migrations without editing contract source: reverse edges are now plannable and applyable via
--to. (#635) - Single-query include aggregates in the SQL ORM client — counts and aggregates on included relations are fetched in one query rather than fanning out. (#596)
planExecutionIdonRuntimeMiddlewareContext, a fresh per-execute()identity letting middleware correlatebeforeExecuteandafterExecutefor the same call. (#605)- Mongo middleware can rewrite query parameters in
beforeExecutebefore they are encoded, restoring parity with the SQL param-mutator seam. (#652) emptyContract({ target })lets contract-space extensions that contribute only migration invariants (e.g. installing a Postgres extension) omit a contract source instead of hand-authoring an empty one. (#651)
- Mongo: optional fields that are
undefinedare omitted when deserializingcreateIndex, instead of being written out. (#580) - Foreign-key referential actions (
onDelete/onUpdate) are now preserved in the schema IR. (#608) - Mongo
db update: adding an optional field to an existing model now applies cleanly — the validator-widening op is classified and applied correctly instead of being gated or dropped. (#624) - The dev→ship transition is fixed: the first
migration planafterdb updatenow succeeds via ref-paired snapshots and an auto-baseline on an empty graph. (#582) prisma-next initscaffolds into the canonicalsrc/prisma/layout, matching the rest of the framework, so fresh projects start in the expected shape. (#581)- In-process contracts built with
defineContractand passed tocreateExecutionContextnow carry the same adapter + driver capability matrix as CLI-emitted contracts. (#602)
- @xxiaoxiong made their first contribution in #580
- @medz made their first contribution in #608