For the complete Prisma documentation index, see llms.txt. A markdown version of any docs page is available by appending .md to its URL.
Reference for the Prisma ORM client lifecycle, transactions, prepared statements, and execution options.
Location: ORM > Reference > Transactions and runtime reference
Prisma ORM 8 renames schema.prisma to contract.prisma. Run npx prisma contract emit, which writes two files next to it: contract.json, which your app imports, and contract.d.ts, which gives you the TypeScript types. A new project keeps all three files in src/prisma/. Every client you create needs contract.json. The query you came for, prisma.user.findMany({ where }), is now db.orm.public.User.where(...).all(). Coming from Prisma ORM 7 lists the packages to install, the commands to run, and the rest of the renamed calls.
db is the client that postgres(...) or mongo(...) returns. db.runtime() gives you the object that actually runs queries, and that object is called the runtime.
Create, connect, and close the client, run transactions, prepare statements, and set per-query execution options, on PostgreSQL and MongoDB. For a task-oriented walkthrough, see the Fundamentals guide to Transactions. For the ways to query inside a transaction, see the ORM client reference, the SQL query builder reference, and the raw queries reference.
Transactions are not available on MongoDB. To run one there, pass your own MongoClient with the mongoClient option and use the driver's session.withTransaction(...), which Transactions on MongoDB shows in code.
Import contract.json and pass it as contractJson, as every example below does. Pass contract only if you already hold a contract object; most code passes contractJson. Supply exactly one.
The <Contract> type argument is what types db.orm and db.sql. contract.d.ts exports it under the name Contract. Pass it yourself whenever you pass contractJson, because TypeScript cannot read a type out of a JSON file. It is inferred only when you use the contract option.
Tell the client which database to use in one of three ways: url, pg, or binding. A binding wraps any of the three choices in one object, with a kind field saying which it is: { kind: 'url', url }, { kind: 'pgPool', pool }, or { kind: 'pgClient', client }. Use it when your code picks the source at run time. Pass exactly one of url, pg, and binding, or leave all three out and pass one to connect().
When you pass a pg pool or client, you own it: call db.close() first, then close it yourself with pool.end(). When you pass a url, the client creates the pool and closes it. Set that pool's timeouts with poolOptions, which has no effect on a pool you supply yourself.
Some types come from a PostgreSQL extension, such as a pgvector.Vector(1536) type in your contract.prisma. Run npm install @prisma/orm-extension-pgvector first. Then import pgvector from '@prisma/orm-extension-pgvector/runtime' and pass extensions: [pgvector].
A middleware is an object with a name and one or more hooks, such as { name: 'no-big-deletes', beforeQuery: (query) => { if (query.sql.includes('DELETE')) throw new Error('blocked') } }. Throw from a hook to block the query. The hooks are beforeCompile, beforeQuery, beforeExecute, interceptQuery, interceptExecute, onRow, afterQuery, and afterExecute. See How middleware works.
Migrations record in your database which contract it matches. With verifyMarker: 'onFirstUse', the default, the first query checks that record and logs a warning on a mismatch, then runs anyway. If you see the warning, run npx prisma db update. verifyMarker: false skips the check.
postgres(options) does not open a connection. The client opens one on first use, so connect() is optional: call it to open the connection up front and fail early.
.sql is the SQL query builder, keyed by PostgreSQL schema, then by table name: db.sql.public.tag. The table name is the model's @@map value, or the model name exactly as written when the model sets no @@map. See the SQL query builder reference. Write raw SQL with db.raw.sql`...` , as the raw queries reference shows.
.enums holds the enum values from your contract, by PostgreSQL schema: db.enums.public.Role. .nativeEnums holds the values of enum types that exist in PostgreSQL itself, created with CREATE TYPE, also by schema. Look the type up by name in brackets: db.nativeEnums.auth['AalLevel'].values is ['aal1', 'aal2', 'aal3'].
These examples assume the file is beside contract.json in src/prisma/, so from anywhere else, change the relative paths. Write ./contract.d exactly like that, with no extension. It resolves under preserve or esnext with bundler, the settings prisma orm init writes, and under nodenext as CommonJS; under nodenext, node18, or node20 as ES modules it does not, and the upgrade guide covers that case. The line import contractJson from './contract.json' with { type: 'json' } needs Node.js 22.18 or newer and TypeScript 5.9 or newer. In tsconfig.json it needs resolveJsonModule: true and one of these module settings, each with its moduleResolution: preserve with bundler, esnext with bundler, or nodenext with nodenext. node18 and node20 also work, with moduleResolution left unset. prisma orm init writes preserve and bundler into new projects. When module is nodenext and package.json has no "type": "module", TypeScript treats the file as CommonJS, and there the import is import contractJson from './contract.json'; with no with part. The Prisma ORM 7 to 8 upgrade guide walks through which setting fits which kind of project. TypeScript types every process.env value as possibly undefined, so the examples write ! after it, the same way prisma orm init does.
Returns a Promise<Runtime>. The runtime is the connected object that runs queries. It has these calls:
runtime.query(built) runs a built query and returns its rows. A built query is the object you get from .build() at the end of a builder or raw chain. See Running a built query.
runtime.execute(built) runs a built query that returns no rows, such as an insert, and returns { affectedRows }. See Running a built query.
runtime.connection() takes one connection out of the pool so that several queries run on the same connection. It returns a promise, and you put the connection back with connection.release(): const connection = await runtime.connection(); try { await connection.query(built) } finally { await connection.release() }. See Manual connection and transaction control.
runtime.prepare(declaration, callback) prepares a statement you run many times with different values. The client has a prepare() too, and the two do the same thing. See Prepared statements (PostgreSQL).
runtime.telemetry() reports how the most recent query went. See runtime.telemetry().
connect() takes the same database options as postgres(...), so a client you created without one can be given its database here: await db.connect({ url: process.env.DATABASE_URL! }), or await db.connect({ binding: { kind: 'url', url } }).
Every error has a code property, and the error reference lists them all. Catch the error and compare error.code with the value shown here, for example if (error.code === 'DRIVER.NOT_CONNECTED').
Call connect() before any query, or not at all. Running a query connects the client, so a connect() after that rejects with an error whose code is DRIVER.ALREADY_CONNECTED. So does a second connect().
On PostgreSQL, runtime() is synchronous: it returns the Runtime directly, not a promise. You can call it before connecting, because the client connects on first use.
Disposal fires at the end of the block the await using declaration lives in, not on the next line. Scope the client to the block where you need it. After the block exits, the client is closed the same way close() closes it: a later connect() rejects with an error whose code is DRIVER.NOT_CONNECTED.
await using needs @types/node installed, or "esnext.disposable" added to the lib array in your tsconfig.json.
{
await using db = postgres<Contract>({ contractJson, url: process.env.DATABASE_URL! });
const tags = await db.runtime().query(db.sql.public.tag.select('id', 'label').build());
} // db is closed here
Create a MongoDB client with mongo(...). The entry point and several lifecycle details differ from PostgreSQL, most notably that runtime() is asynchronous.
Pass the contract with contractJson or contract (supply exactly one), the same way as on PostgreSQL.
Tell the client which database to use in one of four ways: url, uri, mongoClient, or binding. A binding wraps the choices in one object, with a kind field saying which it is: { kind: 'url', url, dbName } or { kind: 'mongoClient', client, dbName }. Use it when your code picks the source at run time, and use the url kind for a uri. Pass exactly one of the four, or leave all four out and pass one to MongoDB connect(). dbName is separate and does not count as one of the four, so { url, dbName } is allowed.
With url, the database name comes from the path of the connection string, as in mongodb://host:27017/app. Add dbName to override that name, or when the string has no name in its path. uri takes the same kind of string, and always needs dbName.
Client ownership: with url or uri, Prisma ORM creates the underlying MongoClient and closes it on close(). With mongoClient, you supplied the client, so Prisma ORM does not close it. Because close() leaves your client open, you can use one MongoClient both through Prisma ORM and in code you write against the mongodb package directly. You close that client yourself.
mongo(options) does not open a connection. The runtime is built on first use, or explicitly via MongoDB connect().
A client that has not connected yet. This is Prisma ORM's own type. The mongodb driver has a class of the same name, so when you import both, name this one db.
.
.orm holds your models by collection name, with no .public in the path: db.orm.users. The collection name is the model's @@map value, or the model name exactly as written when the model sets no @@map. The example schema sets @@map("users") on User, which is why the key here is users. See the ORM client reference.
Returns a Promise<MongoRuntime>. Call connect() before any query, or not at all. Running a query connects the client, so a connect() after that rejects with an error whose code is DRIVER.ALREADY_CONNECTED. So does a second connect().
connect() takes the same database options as mongo(...), so a client you created without one can be given its database here: await db.connect({ url: process.env.MONGODB_URL!, dbName: 'app' }).
(await db.runtime()).query(built) runs any built MongoDB query, including one built by the pipeline builder (db.query).
Returns an AsyncIterableResult, which holds the rows the query returns: await it for an array, or for await to read the rows one at a time as they arrive.
For an update or delete command, use runtime.execute(built), which returns { affectedRows }. Other commands throw an error whose code is RUNTIME.MONGO_STATISTICS_UNSUPPORTED. There is no execute() on the client itself.
// 'posts' is the collection name, the same key you use on `db.orm`const built = db.query.from('posts').build();
const posts = await (await db.runtime()).query(built);
Always call db.close(). runtime.close() leaves the client looking open, and later calls fail.
Returns a Promise<void>. After close(), any further use rejects with an error whose code is DRIVER.NOT_CONNECTED (Mongo client is closed). That covers db.runtime() and ORM access such as db.orm.users.first(), since both go through the same runtime. When you supplied a mongoClient, close() does not close your client (see mongo(options)).
Disposal fires at the end of the block, exactly as on PostgreSQL. After the block exits, a later connect() rejects with an error whose code is DRIVER.NOT_CONNECTED.
{
await using db = mongo<Contract>({ contractJson, url: process.env.MONGODB_URL!, dbName: 'app' });
await db.connect();
// ... run queries ...
} // db is closed here
A transaction groups several writes into one unit: they all commit together, or they all roll back. Use db.transaction(...) in application code. If you are writing a function that takes a runtime as a parameter instead of the client, use withTransaction(...). If you need to run several statements on one connection, get a connection with runtime.connection() and call commit() or rollback() yourself.
Query through tx, not db. tx.orm holds your models. Build queries with tx.sql, then run them with tx.query(...) to get rows back, or tx.execute(...) when the write returns no rows. tx.execute(...) resolves to { affectedRows }, the number of rows the statement changed. Every call on tx uses the same transaction connection, and queries on db run outside the transaction.
db.transaction(...) takes the callback and nothing else. There are no isolationLevel, timeout, or maxWait options, and Prisma ORM offers no way to set the isolation level. Nothing retries a failed transaction for you. Write the retry loop yourself. A transaction has no time limit. PostgreSQL's idle_in_transaction_session_timeout ends one that sits idle between statements, and on PostgreSQL 17 and later transaction_timeout caps its total length. Set either in your connection string or server configuration. See the fundamentals transactions guide.
To put a time limit on the queries inside a transaction, pass a signal to each tx.query(...) and tx.execute(...) call, as the example below does. See RuntimeExecuteOptions.
tx has no transaction() method, so transactions do not nest. If a helper you call starts its own transaction, change it to take tx as a parameter instead. TransactionContext, imported from @prisma/orm-postgres/family-runtime, has query and execute only, so a helper typed with it cannot use tx.orm or tx.sql: async function addTag(tx: TransactionContext) { ... }. There is no exported type for the full tx, so derive it: type Tx = Parameters<Parameters<typeof db.transaction>[0]>[0].
tx.sql is a full SQL builder, keyed the same way as db.sql: use tx.sql.public.<table>, where public is the PostgreSQL schema.
Reads inside the transaction see the transaction's own uncommitted writes.
tx.enums and tx.nativeEnums are the same enum accessors the client has, and .values is the list of an enum's members.
The callback's return value passes through as the result of db.transaction(...).
tx.query(...) returns an AsyncIterableResult that works only while the transaction is open. Read it inside the callback. A result you return and then read after the transaction has ended rejects with an error whose code is RUNTIME.TRANSACTION_CLOSED. To act on a code, catch the error and compare error.code with the string. Every code is listed in the error reference.
try {
await db.transaction(async (tx) => {
await tx.orm.public.Tag.create({ label: 'tx-rollback' });
thrownew Error('deliberate rollback');
});
} catch {
// the tag was rolled back and does not exist
}
await db.transaction(async (tx) => {
const signal = AbortSignal.timeout(5000); // give this statement five secondsawait tx.execute(
tx.sql.public.tag.insert([{ id: crypto.randomUUID(), label: 'tx-sql-insert' }]).build(),
{ signal },
);
});
[!WARNING]
An escaped
AsyncIterableResult
throws after the transaction ends
Collect the rows inside the callback by awaiting the result there, and return the array instead. A result is tied to the transaction connection, so if you return one from the callback and read it after the transaction has committed, it rejects with an error whose code is RUNTIME.TRANSACTION_CLOSED, before yielding any row.
Imported from @prisma/orm-postgres/family-runtime, along with the Runtime type.
The callback receives a transaction handle whose only methods are query and execute. Unlike db.transaction(...)'s tx, it has no .orm or .sql. Pass a SQL builder into your function as well, such as the client's db.sql, and build your queries with that.
Commits on return, rolls back on throw, the same as db.transaction(...).
The lowest level: acquire a connection, open a transaction on it, commit or roll back yourself, and return the connection to the pool. Use it when you need several statements on one connection, such as a read and then a write with no transaction around them.
runtime.connection() returns a dedicated connection. connection.transaction() opens a transaction on it. Run built queries with transaction.query(...) for rows or transaction.execute(...) for writes that return no rows, then call transaction.commit() or transaction.rollback(). Always release() the connection when done, to return it to the pool. Commit or roll back on every path first, then release: the examples nest a try for the transaction inside the try whose finally releases the connection, so a statement that throws neither leaves the transaction open nor leaks the connection.
connection.destroy() throws that connection away instead of returning it to the pool. Use destroy() only for a connection you no longer trust.
const connection = await runtime.connection();
await connection.destroy();
// the runtime stays healthy and opens a replacement connection on the next query
[!NOTE]
MongoDB transactions are not available in Prisma ORM yet
There is no db.transaction(...) on the MongoDB client, and the MongoDB runtime has no connection(), prepare(), or telemetry(): it has query, execute, and close and nothing else. Single-document write operations are atomic on their own, while multi-document transactions through Prisma ORM are planned, not shipped. To run one today, create your own MongoClient, pass it as the mongoClient option when you create the client, and group the writes in session.withTransaction(...), as the Fundamentals transactions guide shows. The reading rules under AsyncIterableResult apply to MongoDB too.
A prepared statement compiles a query once against a declaration of its parameters, then runs it repeatedly with different values. Prepared statements are not available on MongoDB.
The declaration maps each parameter name to a type id, for example { label: 'pg/text@1' }. Use db.sql.public.<table>.columns.<column>.codecId to get any column's id. The table on the raw queries page lists the common ones.
The callback takes one argument, the declared params, and returns the built query, which is what .build() gives you. SqlQueryPlan in the tables below is the type of a built query. Build it with a SQL builder you already hold, such as db.sql. In the where((f, fns) => ...) calls below, f holds the table's columns and fns holds the comparison functions.
A declared parameter that the callback never uses is rejected at prepare time with an error whose code is RUNTIME.PREPARE_UNUSED_PARAM, with details.unused listing the parameter names you declared but did not use. The rejection happens at prepare(), before any execution.
The resulting PreparedStatement runs via ps.query(target, params); see PreparedStatement.query.
The callback takes one argument, the declared params, as it does on runtime.prepare(...). Build the query with the client's own db.sql or db.orm.
The callback can return a built SQL query, or an ORM query that ends in .prepared.all(), .prepared.first(), or .prepared.aggregate(...). .prepared always goes right before that last call, so a grouped aggregate is written db.orm.public.Post.groupBy('userId').prepared.aggregate((agg) => ({ posts: agg.count() })).
The ORM query is built once. db.prepare(...) resolves to a prepared query, here called ps, and you run it with ps.query(db.runtime(), values), where values holds a value for each declared parameter. A connection or a transaction also works in place of db.runtime(). Each run uses the new values and returns what the same call without .prepared returns:
all() returns the rows, as an AsyncIterableResult that you await for an array.
first() returns a promise of one row, or of null when no row matches.
aggregate(...) returns a promise of the aggregate object.
groupBy(...) followed by aggregate(...) returns a promise of an array with one object per group.
prepare() starts the connection itself. Call db.runtime() for the runtime. db.connect() would throw an error whose code is DRIVER.ALREADY_CONNECTED, because prepare() already connected.
In an ORM query, a prepared parameter works in where(...) filters, in the callback you pass to include(...), and in .limit(...) and .offset(...), so the same statement can page through results. Declare a page size or offset as 'pg/int4@1', as in { take: 'pg/int4@1' }, and pass params.take to .limit(...). A text type id such as 'pg/text@1', or a nullable one such as { codecId: 'pg/int4@1', nullable: true }, is a type error there.
A query that returns rows prepares into a PreparedStatement, which you run with query(...). Some queries return a row count instead of rows. End the query with .affectedCount(). Preparing one gives you a PreparedExecution, which you run with execute(target, params). It resolves to { affectedRows }. See the raw queries reference:
The statement's rows. await the result for an array.
.
A prepared ORM query from db.prepare(...) is different: its query(...) returns what the same ORM call returns, so a prepared first() gives a promise of one row or null.
runtime.query(...), runtime.execute(...), tx.query(...), tx.execute(...), connection.query(...), and connection.execute(...) all take a RuntimeExecuteOptions object as their second argument. A prepared statement takes it as a third argument, after target and params.
signal is an AbortSignal for per-query cancellation. A signal that is already aborted when you call query(...) rejects before any row is fetched, with an error whose code is RUNTIME.ABORTED. The error's cause is the signal's reason, exactly as you passed it to controller.abort(...).
An abort that lands after rows have started arriving ends the stream with the same RUNTIME.ABORTED error. details.phase says where the abort landed.
Inside db.transaction(...) the aborted statement throws, so your callback throws and the transaction rolls back.
PostgreSQL only. telemetry() does not exist on the MongoDB runtime. Read it after a query to log how long that query took and whether it succeeded.
telemetry() returns null on a freshly-connected runtime, before any query has run. After a query, it returns an object of the shape { lane, target: 'postgres', fingerprint, outcome, durationMs? }. The object reflects only the most recent query, not a running history.
lane says which API built the query, such as orm-client for db.orm or raw for db.raw.sql.
Every run of the same query text shares one fingerprint, whatever values you pass, so you can group runs of one query together.
outcome is 'success' or 'runtime-error'. durationMs is how long the query took. It is optional in the type, and it is set for every query the runtime ran.
all(), createAll(), and the runtime's query(...) return an AsyncIterableResult. all() and query(...) read rows, and createAll() writes rows and returns the ones it wrote. await the result to collect an array, or for await it to take rows one at a time.
Use await, and use for await only to handle rows as they arrive. On PostgreSQL it does not reduce memory, because every row is loaded first.
Pick await or for await for a given result and do not mix the two. Calling .toArray() does the same thing as await. Re-awaiting a result you already awaited is safe and returns the same array, but switching between await and for await, or looping a second time with for await, throws an error whose code is RUNTIME.ITERATOR_CONSUMED. For the full rules on reading a result, shared identically by PostgreSQL and MongoDB, see AsyncIterableResult in the ORM client reference.
Error reference: Every structured error code Prisma ORM can emit, by namespace, with the condition that raises it.
Migration API: Every method and helper a migration.ts can use: the Migration class, the PostgreSQL operations, the column and constraint helpers, rawSql, and the MongoDB operations.
ORM client reference: Reference for the Prisma ORM client's query, mutation, filter, and aggregate methods.
Pipeline builder reference: Reference for the Prisma ORM MongoDB pipeline builder's stages, accumulators, expression helpers, and write methods.
Raw queries reference: Reference for Prisma ORM raw queries: PostgreSQL raw SQL and MongoDB raw commands.