Create custom Prisma Client queries
You can use the query
Prisma Client extensions component type to hook into the query life-cycle and modify an incoming query or its result. We introduced this feature in version 4.7.0.
You can use Prisma Client extensions query
component to create independent clients. This provides an alternative to middlewares. You can bind one client to a specific filter or user, and another client to another filter or user. For example, you might do this to get user isolation in a row-level security (RLS) extension. In addition, unlike middlewares the query
extension component gives you end-to-end type safety. Learn more about query
extensions versus middlewares.
Enable the preview feature
Before you create Prisma Client extensions, you must enable the clientExtensions
feature flag in the generator
block of your schema.prisma
file, as follows:
generator client {provider = "prisma-client-js"previewFeatures = ["clientExtensions"]}
Extend Prisma Client query operations
Use the $extends
client-level method to create an extended client. An extended client is a variant of the standard Prisma Client that is wrapped by one or more extensions.
Use the query
extension component to modify queries. You can modify a custom query in the following:
- A specific operation in a specific model
- A specific operation in all models of your schema
- All operations in a specific model
- All operations in all models of your schema
- A specific top-level raw query operation
To create a custom query, use the following structure:
const xprisma = prisma.$extends({name?: 'name',query?: {user: { ... } // in this case, we add a query to the `user` model},});
The properties are as follows:
name
: (optional) specifies a name for the extension that appears in error logs.query
: defines a custom query.
Modify a specific operation in a specific model
The query
object can contain functions that map to the names of the Prisma Client operations, such as findUnique
, findFirst
, findMany
, count
, and create
. The following example modifies user.findMany
to a use a customized query that finds only users who are older than 18 years:
const xprisma = prisma.$extends({query: {user: {async findMany({ model, operation, args, query }) {// set `age` and fill with the rest of `where`args.where = { age: { gt: 18 }, ...args.where }return query(args)},},},})await xprisma.user.findMany() // returns users whose age is greater than 18
In the above example, a call to xprisma.user.findMany
triggers query.user.findMany
. Each callback receives a type-safe { model, operation, args, query }
object that describes the query. This object has the following properties:
model
: the name of the containing model for the query that we want to extend.In the above example, the
model
is a string of type"User"
.operation
: the name of the operation being extended and executed.In the above example, the
operation
is a string of type"findMany"
.args
: the specific query input information to be extended.This is a type-safe object that you can mutate before the query happens. You can mutate any of the properties in
args
. Exception: you cannot mutateinclude
orselect
because that would change the expected output type and break type safety.query
: a promise for the result of the query.- You can use
await
and then mutate the result of this promise, because its value is type-safe. TypeScript catches any unsafe mutations on the object.
- You can use
Modify a specific operation in all models of your schema
To extend the queries in all the models of your schema, use $allModels
instead of a specific model name. For example:
const xprisma = prisma.$extends({query: {$allModels: {async findMany({ model, operation, args, query }) {// set `take` and fill with the rest of `args`args = { take: 100, ...args }return query(args)},},},})
Modify all operations in a specific model
Use $allOperations
to extend all operations in a specific model.
For example, the following code applies a custom query to all operations on the user
model:
const xprisma = prisma.$extends({query: {user: {$allOperations({ model, operation, args, query }) {// handle all operationsreturn query(args)},},},})
Modify all operations in all models of your schema
Use $allModels
and $allOperations
to extend all operations in all models of your schema.
To apply a custom query to all operations on all models of your schema:
const xprisma = prisma.$extends({query: {$allModels: {$allOperations({ model, operation, args, query }) {// handle all operationsreturn query(args)},},},})
Modify a top-level raw query operation
To apply custom behavior to a specific top-level raw query operation, use the name of a top-level raw query function instead of a model name:
Mutate the result of a query
You can use await
and then mutate the result of the query
promise.
const xprisma = prisma.$extends({query: {user: {async findFirst({ model, operation, args, query }) {const user = await query(args)if (user.password !== undefined) {user.password = '******'}return user},},},})
We include the above example to show that this is possible. However, for performance reasons we recommend that you use the result
component type to override existing fields. The result
component type usually gives better performance in this situation because it computes only on access. The query
component type computes after query execution.
Wrap a query into a batch transaction
You can wrap your extended queries into a batch transaction. For example, you can use this to enact row-level security (RLS).
The following example extends findFirst
so that it runs in a batch transaction.
const xprisma = prisma.$extends({query: {user: {// Get the input `args` and a callback to `query`async findFirst({ args, query, operation }) {const [result] = await prisma.$transaction([query(args)]) // wrap the query in a batch transaction, and destructure the result to return an arrayreturn result // return the first result found in the array},},},})
Query extensions versus middlewares
You can use query extensions or middlewares to hook into the query life-cycle and modify an incoming query or its result. Client extensions and middlewares differ in the following ways:
- Middlewares always apply globally to the same client. Client extensions are isolated, unless you deliberately combine them. Learn more about client extensions.
- For example, in a row-level security (RLS) scenario, you can keep each user in an entirely separate client. With middlewares, all users are active in the same client.
- During application execution, with extensions you can choose from one or more extended clients, or the standard Prisma Client. With middlewares, you cannot choose which client to use, because there is only one global client.
- Extensions benefit from end-to-end type safety and inference, but middlewares don't.
You can use Prisma Client extensions in all scenarios where middlewares can be used.
If you use the query extension component and middlewares
If you use the query
extension component and middlewares in your project, then the following rules and priorities apply:
- In your application code, you must declare all your middlewares on the main Prisma Client instance. You cannot declare them on an extended client.
- In situations where middlewares and extensions with a
query
component execute, Prisma Client executes the middlewares before it executes the extensions with thequery
component. Prisma Client executes the individual middlewares and extensions in the order in which you instantiated them with$use
or$extends
.