Overview

Client extensions are the supported mechanism for cross-cutting behavior in Prisma. prisma.$extends() returns a new client that layers extra behavior over the base client through four components: query, result, model, and client. Extensions replaced the $use middleware API, which was deprecated in v4.16.0 and removed in v6.14.0; it does not exist in Prisma 7. Use extensions for soft-delete filters, computed fields, audit logging, and custom helper methods. For the client lifecycle that holds these extensions, see prisma-client.

Assign the extended client; the base is immutable

$extends does not mutate the client. It returns a new instance with the extension applied.

const prisma = base.$extends({
  /* components */
})
  • Export and use the extended prisma, not base. Queries on base skip the extension.
  • Chain $extends calls to compose extensions. Each layer wraps the previous one in order.
  • Type inference flows through the chain, so custom methods and computed fields are fully typed downstream. See typescript.

Use the query component to intercept and rewrite queries

The query component runs around each operation. It is the soft-delete and tenant-isolation tool that $use used to provide.

const prisma = base.$extends({
  query: {
    post: {
      findMany({ args, query }) {
        args.where = { ...args.where, deletedAt: null }
        return query(args)
      },
    },
  },
})
  • Scope hooks to a model and operation (post.findMany) or to $allOperations across a model.
  • Call query(args) exactly once to forward to the database. Mutate args before the call to filter or default values.
  • Do not cache inside a query hook. It fires on every matching call, which makes invalidation unpredictable.

Use the result component to add computed fields

The result component adds derived fields without a database round trip.

const prisma = base.$extends({
  result: {
    user: {
      fullName: {
        needs: { firstName: true, lastName: true },
        compute(user) {
          return `${user.firstName} ${user.lastName}`
        },
      },
    },
  },
})
  • needs declares the columns the computed field depends on. Prisma selects them automatically when the field is read.
  • Computed fields are virtual; they are not stored and cannot be filtered on in where. Promote to a real column when you need to query it. See prisma-schema.

Use model and client for custom methods

The model component adds methods to a specific model; client adds top-level methods to the client.

const prisma = base.$extends({
  model: {
    user: {
      async signUp(email: string) {
        return base.user.create({ data: { email } })
      },
    },
  },
  client: {
    $health() {
      return base.$queryRaw`SELECT 1`
    },
  },
})
  • Use model methods for domain operations that belong to one entity (user.signUp).
  • Use client methods for cross-cutting helpers (health checks, raw maintenance queries). See prisma-raw-queries.
  • Extensions interact with transactions: inside an interactive $transaction, the tx client carries the same extensions. See prisma-transactions.