December 19, 2022
Prisma Client Just Became a Lot More Flexible: Prisma Client Extensions (Preview)
Prisma Client extensions (in Preview) enable many new use cases. This article will explore various ways you can use extensions to add custom functionality to Prisma Client.
Table Of Contents
- Introduction
- Using Prisma Client extensions
- Sample use cases
- Example: Computed fields
- Example: Transformed fields
- Example: Obfuscated fields
- Example: Instance methods
- Example: Static methods
- Example: Model filters
- Example: Readonly client
- Example: Input transformation
- Example: Input validation
- Example: JSON field types
- Example: Query logging
- Example: Retry transactions
- Example: Callback-free interactive transactions
- Example: Audit log context
- Example: Row level security
- Tell us what you think
Introduction
Prisma Client extensions provide a powerful new way to add functionality to Prisma in a type-safe manner. With them, you'll be able to create simple, flexible solutions to problems that aren't natively supported by the ORM (yet). You can define extensions in TypeScript or JavaScript, compose them, and even create multiple lightweight Prisma Client instances with different extensions.
When you're ready, you can share your extensions with the community as code snippets or by packaging them and publishing them to npm. This article will show you what's possible with extensions and hopefully inspires you to create and share your own!
Note: We believe Prisma Client extensions will open up many new possibilities when working with Prisma. However, just because a problem can be solved with an extension doesn't mean it won't ever be addressed with a first-class feature. One of our goals is to experiment and explore solutions with our community before integrating them into Prisma natively.
Using Prisma Client extensions
To use Prisma Client extensions, you'll need to first enable the clientExtensions
preview feature in your Prisma schema file:
Then, you can call the $extends
method on a Prisma Client instance. This will return a new, "extended" client instance without modifying the original instance. You can chain calls to $extends
to use multiple extensions, and create separate instances with different extensions:
The components of an extension
There are four different types of components that can be included in an extension:
- Model components allow you to add new methods to models. This is a convenient way to add new operations alongside default methods like
findMany
,create
, etc. You can use this as a repository of common query methods, encapsulate business logic for models, or do anything you might do with a static method on a class. - Client components can be used to add new top-level methods to Prisma Client itself. Use this to extend the client with functionality that isn't tied to specific models.
- Query components let you hook into the query lifecycle and perform side effects, modify query arguments, or alter the results in a type-safe way. These are an alternative to middleware that provide full type safety and can be applied ad-hoc to different extended client instances.
- Result components add custom fields and methods to query result objects. These allow you to implement virtual / computed fields, define business logic for model instances in a single place, and transform the data returned by your queries.
A single extension can include one or more components, as well as an optional name to display in error messages:
To see the full syntax for defining each type of extension component, please refer to the docs.
Sharing an extension
You can use the Prisma.defineExtension
utility to define an extension that can be shared with other users:
When publishing shared extensions to npm, we recommend using the prisma-extension-<package-name>
convention. This will make it easier for users to find and install your extension in their apps.
For example, if you publish an extension with the package name prisma-extension-find-or-create
, users can install it like:
And then use the extension in their app:
Read our documentation on sharing extensions for more details.
Sample use cases
We have compiled a list of use cases that might be solved with extensions, and we've created some examples of how these extensions could be written. Let's take a look at these use cases and their implementations:
Note: Prisma Client extensions are still in Preview, and some of the examples below may have some limitations. Where known, caveats are listed in the example
README
files on GitHub.
Example: Computed fields
View full example on GitHub
This example demonstrates how to create a Prisma Client extension that adds virtual / computed fields to a Prisma model. These fields are not included in the database, but rather are computed at runtime.
Computed fields are type-safe and may return anything from simple values to complex objects, or even functions that can act as instance methods for your models. Computed fields must specify which other fields they depend on, and they may be composed / reused by other computed fields.
Example: Transformed fields
View full example on GitHub
This example shows how to use a Prisma Client extension to transform fields in results returned by Prisma queries. In the example, a date
field is transformed to a relative string for a specific locale.
This shows a way to implement internationalization (i18n) at the data access layer in your application. However, this technique could allow you to implement any kind of custom transformation or serialization/deserialization of fields on your query results.
Example: Obfuscated fields
View full example on GitHub
This example is a special case for the previous Transformed fields example. It uses an extension to hide a sensitive password
field on a User
model. The password
column is not included in selected columns in the underlying SQL queries, and it will resolve to undefined
when accessed on a user result object. It could also resolve to any other value, such as an obfuscated string like "********"
.
Example: Instance methods
View full example on GitHub
This example shows how to add an Active Record-like interface to Prisma result objects. It uses a result
extension to add save
and delete
methods directly to User
model objects returned by Prisma Client methods.
This technique can be used to customize Prisma result objects with behavior, analogous to adding instance methods to model classes.
Example: Static methods
View full example on GitHub
This example demonstrates how to create a Prisma Client extension that adds signUp()
and findManyByDomain()
methods to a User model.
This technique can be used to abstract the logic for common queries / operations, create repository-like interfaces, or do anything you might do with a static class method.
Example: Model filters
View full example on GitHub
This example demonstrates a Prisma Client extension which adds reusable filters for a model that can be composed and passed to a query's where
condition. Complex, frequently used filtering conditions can be written once and accessed in many queries through the extended Prisma Client instance.
Example: Readonly client
View full example on GitHub
This example creates a client that only allows read operations like findMany
and count
, not write operations like create
or update
. Calling write operations will result in an error at runtime and at compile time with TypeScript.
Example: Input transformation
View full example on GitHub
This example creates an extended client instance which modifies query arguments to only include published
posts.
Since query
extensions allow modifying query arguments, it's possible to apply various kinds of default filters with this approach.
Example: Input validation
View full example on GitHub
This example uses Prisma Client extensions to perform custom runtime validations when creating and updating database objects. It uses a Zod runtime schema to check that the data passed to Prisma write methods is valid.
This could be used to sanitize user input or otherwise deny mutations that do not meet some criteria defined by your business logic rules.
Example: JSON Field Types
View full example on GitHub
This next example combines the approaches shown in the Input validation and Transformed fields examples to provide static and runtime types for a Json
field. It uses Zod to parse the field data and infer static TypeScript types.
This example includes a User
model with a JSON profile field, which has a sparse structure that may vary between users. The extension has two parts:
- A
result
extension that adds a computedprofile
field. This field uses theProfile
Zod schema to parse the underlying untypedprofile
field. TypeScript infers the static data type from the parser, so query results have both static and runtime type safety. - A
query
extension that parses theprofile
field on input data for theUser
model's write methods likecreate
andupdate
.
Example: Query logging
View full example on GitHub
This example shows how to use Prisma Client extensions to perform similar tasks to middleware. In this example, a query
extension tracks the time it takes to fulfill each query, and logs the results along with the query and arguments themselves.
This technique could be used to perform generic logging, emit events, track usage, etc.
Note: You may be interested in the OpenTelemetry tracing and Metrics features (both in Preview), which provide detailed insights into performance and how Prisma interacts with the database.
Example: Retry transactions
View full example on GitHub
This example shows how to use a Prisma Client extension to automatically retry transactions that fail due to a write conflict / deadlock timeout. Failed transactions will be retried with exponential backoff and jitter to reduce contention under heavy traffic.
Example: Callback-free interactive transactions
View full example on GitHub
This example shows a Prisma Client extension which adds a new API for starting interactive transactions without callbacks.
This gives you the full power of interactive transactions (such as read–modify–write cycles), but in a more imperative API. This may be more convenient than the normal callback-style API for interactive transactions in some scenarios.
Example: Audit log context
View full example on GitHub
This example shows how to use a Prisma Client extension to provide the current application user's ID as context to an audit log trigger in Postgres. User IDs are included in an audit trail tracking every change to rows in a table.
A detailed explanation of this solution can be found in the example's README
on GitHub.
Example: Row level security
View full example on GitHub
This example shows how to use a Prisma Client extension to isolate data between tenants in a multi-tenant app using Row Level Security (RLS) in Postgres.
A detailed explanation of this solution can be found in the example's README
on GitHub.
Tell us what you think
We hope you are as excited as we are about the possibilities that Prisma Client extensions create!
💡 You can share your feedback with us in this GitHub issue.
Don’t miss the next post!
Sign up for the Prisma Newsletter