From c71aa6616b2035b0c7df9f54c6fc75cc686c4b6c Mon Sep 17 00:00:00 2001 From: willbot Date: Thu, 17 Sep 2026 10:52:26 +0200 Subject: [PATCH 01/10] docs(orm): extensions fact check against rc.11 Checked every claim on the two extensions pages against prisma/orm at v8.0.0-rc.11 (ff47560c9f). One correction: the control and runtime entrypoint layout is the usual one, not universal, because the cache middleware publishes a single entrypoint and Supabase has no control subpath. Co-Authored-By: Claude Fable 5.1 Signed-off-by: willbot Signed-off-by: Will Madden --- apps/docs/content/docs/orm/extensions/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/content/docs/orm/extensions/index.mdx b/apps/docs/content/docs/orm/extensions/index.mdx index fc7add35ecb..e56c73bf9f5 100644 --- a/apps/docs/content/docs/orm/extensions/index.mdx +++ b/apps/docs/content/docs/orm/extensions/index.mdx @@ -35,7 +35,7 @@ Entries marked experimental work today, but their API can change between release ## Publish your own -An extension is a versioned npm package with a `control` entrypoint for the config and a `runtime` entrypoint for the client. The same layout adds a database: `@prisma/orm-postgres` is the reference for a SQL database and `@prisma/orm-mongo` for a document database. The [call for extension authors](https://www.prisma.io/blog/prisma-next-call-for-extension-authors) describes the layout. To write middleware, follow [Authoring custom middleware](/orm/middleware/authoring-custom-middleware). +An extension is usually a versioned npm package with a `control` entrypoint for the config and a `runtime` entrypoint for the client; middleware ships a single entrypoint instead. The same layout adds a database: `@prisma/orm-postgres` is the reference for a SQL database and `@prisma/orm-mongo` for a document database. The [call for extension authors](https://www.prisma.io/blog/prisma-next-call-for-extension-authors) describes the layout. To write middleware, follow [Authoring custom middleware](/orm/middleware/authoring-custom-middleware). When the package is on npm, [submit it to the directory](https://www.prisma.io/extensions/submit). The form validates the entry, checks that the package exists on npm, and opens a pull request for a maintainer to review. From 5cd1431a7aabbc221d8542e9c305ad4f0cd6922a Mon Sep 17 00:00:00 2001 From: willbot Date: Thu, 17 Sep 2026 10:55:32 +0200 Subject: [PATCH 02/10] docs(orm): extensions real run against the published packages Ran the using-extensions page end to end in a create-prisma scaffold against a local @prisma/dev server that has the vector extension. The page went from editing the contract straight to db init; without contract emit in between, migration plan is a no-op and db init builds the old tables, so the page now says to run contract emit first. Commands are spelled npx prisma . Co-Authored-By: Claude Fable 5.1 Signed-off-by: willbot Signed-off-by: Will Madden --- apps/docs/content/docs/orm/extensions/using-extensions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/content/docs/orm/extensions/using-extensions.mdx b/apps/docs/content/docs/orm/extensions/using-extensions.mdx index 2e7f7e56927..884e7a9e509 100644 --- a/apps/docs/content/docs/orm/extensions/using-extensions.mdx +++ b/apps/docs/content/docs/orm/extensions/using-extensions.mdx @@ -73,7 +73,7 @@ model Post { ## 5. Apply and query -Run `npx prisma@latest db init` (or `db update` on an existing database). The extension ships its own migration, so this step runs `CREATE EXTENSION IF NOT EXISTS vector` for you. If `db init` reports a contract-space layout violation instead, run `npx prisma@latest migration plan` once: it writes the extension's baseline migration under `migrations//`, and `db init` then proceeds. Then query with the operations the extension adds: +Run `npx prisma contract emit` so that `contract.json` and `contract.d.ts` pick up the new column, then `npx prisma db init` (or `db update` on an existing database). The extension ships its own migration, so this step runs `CREATE EXTENSION IF NOT EXISTS vector` for you. If `db init` reports a contract-space layout violation instead, run `npx prisma migration plan` once: it writes the extension's baseline migration under `migrations//`, and `db init` then proceeds. Then query with the operations the extension adds: ```ts title="src/prisma/similarity-search.ts" const plan = db.sql.public.post From e43d86ccb6128cf822c9bd062d1aa7f338768d49 Mon Sep 17 00:00:00 2001 From: willbot Date: Thu, 17 Sep 2026 11:00:10 +0200 Subject: [PATCH 03/10] docs(orm): extensions explanation pass Rewrote the prose on the two extensions pages by the rules in references/explain-not-state.md: meaning before mechanism, conditions as scenarios, connectives between adjacent facts, no counting lead-ins. Banned vocabulary (capability, surface, wired, carries, runtime as a noun) replaced. The Capabilities heading is now What an extension declares with the old anchor pinned. Facts, code blocks, and the generated catalog tables are unchanged. Co-Authored-By: Claude Fable 5.1 Signed-off-by: willbot Signed-off-by: Will Madden --- .../content/docs/orm/extensions/index.mdx | 12 +++--- .../docs/orm/extensions/using-extensions.mdx | 41 +++++++++---------- 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/apps/docs/content/docs/orm/extensions/index.mdx b/apps/docs/content/docs/orm/extensions/index.mdx index e56c73bf9f5..982ccbcb9a5 100644 --- a/apps/docs/content/docs/orm/extensions/index.mdx +++ b/apps/docs/content/docs/orm/extensions/index.mdx @@ -6,13 +6,13 @@ metaTitle: Prisma ORM extensions metaDescription: 'The catalog of Prisma ORM extensions with a one-line summary of each, how to register one, and how to publish your own.' --- -An extension is an npm package that adds a capability to Prisma ORM. The database packages are extensions, and so are column types such as vectors and geometries, index types, query operations, and the middleware that runs around each query. +An extension is an npm package that adds something Prisma ORM does not do on its own. The packages that add support for a database are extensions themselves, and so are column types such as vectors and geometries, index types, query operations, and the middleware that runs around each query. -You register an extension in two places: in `prisma.config.ts`, so the CLI can emit your contract and plan migrations, and on the client, so your queries can use the new types and operations. [Using extensions](/orm/extensions/using-extensions) walks through both with pgvector. Middleware registers on the client only; see [How middleware works](/orm/middleware/how-middleware-works). +Installing the package is not the whole job, because you also register the extension where it is used. You register it in `prisma.config.ts` so that `prisma contract emit` can write `contract.json` and `contract.d.ts` from your contract and the migration commands can plan your migrations, and you register it again when you create the client so that your queries can use the new types and operations. [Using extensions](/orm/extensions/using-extensions) walks through both registrations with pgvector. Middleware is the exception, because you register it on the client only, and [How middleware works](/orm/middleware/how-middleware-works) covers that. ## Catalog -This table is generated from the registry behind the [extension directory](https://www.prisma.io/extensions), which also has the install command and registration snippets for each entry. +Every entry below comes from the registry behind the [extension directory](https://www.prisma.io/extensions), where you will also find the install command and the registration snippets for each one. {/* extensions-catalog:start */} | Name | What it adds | Package | Databases | By | @@ -31,13 +31,13 @@ This table is generated from the registry behind the [extension directory](https | [zod-json](https://www.prisma.io/extensions/zod-json) | Typed JSON columns described and enforced by a zod schema. | `prisma-orm-extension-zod-json` | PostgreSQL | [Omar Dulaimi](https://github.com/omar-dulaimi) | {/* extensions-catalog:end */} -Entries marked experimental work today, but their API can change between release candidates. +An entry marked experimental works today, but its API can change between release candidates. ## Publish your own -An extension is usually a versioned npm package with a `control` entrypoint for the config and a `runtime` entrypoint for the client; middleware ships a single entrypoint instead. The same layout adds a database: `@prisma/orm-postgres` is the reference for a SQL database and `@prisma/orm-mongo` for a document database. The [call for extension authors](https://www.prisma.io/blog/prisma-next-call-for-extension-authors) describes the layout. To write middleware, follow [Authoring custom middleware](/orm/middleware/authoring-custom-middleware). +An extension you write yourself is usually a versioned npm package with one entrypoint for each of the places it gets registered: a `control` entrypoint for the config and a `runtime` entrypoint for the client. Middleware is again the exception and ships a single entrypoint. Adding a whole database uses that same layout, so if that is what you are writing, `@prisma/orm-postgres` is the one to follow for a SQL database and `@prisma/orm-mongo` for a document database. The [call for extension authors](https://www.prisma.io/blog/prisma-next-call-for-extension-authors) describes the layout. To write middleware, follow [Authoring custom middleware](/orm/middleware/authoring-custom-middleware). -When the package is on npm, [submit it to the directory](https://www.prisma.io/extensions/submit). The form validates the entry, checks that the package exists on npm, and opens a pull request for a maintainer to review. +Once your package is on npm, [submit it to the directory](https://www.prisma.io/extensions/submit). The form checks that your entry is valid and that the package really exists on npm, then opens a pull request for a maintainer to review. ## See also diff --git a/apps/docs/content/docs/orm/extensions/using-extensions.mdx b/apps/docs/content/docs/orm/extensions/using-extensions.mdx index 884e7a9e509..41ec8b5ebee 100644 --- a/apps/docs/content/docs/orm/extensions/using-extensions.mdx +++ b/apps/docs/content/docs/orm/extensions/using-extensions.mdx @@ -1,16 +1,16 @@ --- title: Using extensions -description: 'Extensions add database capabilities like vector search, geospatial data, and full-text search to a Prisma ORM project.' +description: 'Extensions add database features like vector search, geospatial data, and full-text search to a Prisma ORM project.' url: /orm/extensions/using-extensions metaTitle: Using extensions in Prisma ORM metaDescription: 'Add an extension to a Prisma ORM project, from install to first query, and browse the catalog of available extensions such as pgvector, PostGIS, and ParadeDB.' --- -An extension is a package that adds a database capability Prisma ORM does not have out of the box: new column types, query operations, and index types, along with the migrations that install the underlying database feature. Vector search, geospatial data, full-text search, typed JSON, and provider-specific integrations are all added through extensions. +An extension is a package that teaches Prisma ORM a database feature it does not know out of the box. Installing one gives you new column types to declare in your schema, new query operations to call, and new index types, along with the migrations that install the feature in the database itself. Vector search, geospatial data, full-text search, typed JSON, and provider-specific integrations all reach your project this way. -Use an extension when you need one of these database features while keeping typed schema declarations, generated TypeScript, migration support, and query helpers in your Prisma ORM project. +Reach for an extension when your application needs one of those database features and you still want everything Prisma ORM gives you for the rest of your data: typed schema declarations, generated TypeScript, migration support, and query helpers. -To add an extension, install its package and register it in two places: the config and the client. The steps below use pgvector, the vector search extension, as the example. Supabase is wired differently; see [the note on the catalog](#available-extensions). +Adding an extension means installing its package and then registering it in two places, your config file and your client. The steps here use pgvector, the vector search extension, as the example. Supabase is set up differently, so if that is the extension you are adding, read [the note under the catalog](#available-extensions) first. ## 1. Install the package @@ -20,7 +20,7 @@ npm install @prisma/orm-extension-pgvector ## 2. Register it in the config -Prisma ORM uses this registration when it emits your contract and plans migrations: +Your config file is what the `prisma` command line tools read, so the registration here is the one they use: it is how `prisma contract emit` knows about the extension's types when it writes your contract, and how the migration commands know about the migrations the extension ships. ```ts title="prisma.config.ts" import { definePrismaConfig } from 'prisma/config'; @@ -40,7 +40,7 @@ export default definePrismaConfig({ ## 3. Register it on the client -Prisma ORM uses this registration when your app runs queries: it adds the extension's query operations and value types: +The second registration is the one your application uses while it is running: passing the extension to `postgres(...)` is what adds the extension's query operations and value types to the `db` client you write queries against. ```ts title="src/prisma/db.ts" import pgvector from '@prisma/orm-extension-pgvector/runtime'; @@ -57,7 +57,7 @@ export const db = postgres({ ## 4. Use the new type in your schema -The extension's types are now available in your contract, so declare a vector column with an explicit dimension: +Now that pgvector is registered in the config, its types are available in your contract, so you can give a column a vector type. A vector column needs an explicit number of dimensions, so name the type once under `types` and then use that name on the model: ```prisma title="src/prisma/contract.prisma" types { @@ -73,7 +73,7 @@ model Post { ## 5. Apply and query -Run `npx prisma contract emit` so that `contract.json` and `contract.d.ts` pick up the new column, then `npx prisma db init` (or `db update` on an existing database). The extension ships its own migration, so this step runs `CREATE EXTENSION IF NOT EXISTS vector` for you. If `db init` reports a contract-space layout violation instead, run `npx prisma migration plan` once: it writes the extension's baseline migration under `migrations//`, and `db init` then proceeds. Then query with the operations the extension adds: +Run `npx prisma contract emit` first, so that `contract.json` and `contract.d.ts` pick up the new column, and then run `npx prisma db init` on a new database, or `npx prisma db update` if the database already exists. You do not have to install the pgvector feature in PostgreSQL yourself, because the extension ships its own migration and this step runs `CREATE EXTENSION IF NOT EXISTS vector` for you. If `db init` reports a contract-space layout violation instead of succeeding, run `npx prisma migration plan` once. That writes the extension's baseline migration under `migrations//`, and `db init` goes through after that. With the column in your database, you can query it with the operations the extension adds: ```ts title="src/prisma/similarity-search.ts" const plan = db.sql.public.post @@ -86,26 +86,23 @@ const plan = db.sql.public.post const similar = await db.runtime().query(plan); ``` -pgvector contributes `fns.cosineSimilarity` alongside `fns.cosineDistance`. Both take two vector expressions and return a `float8`; similarity sorts descending where distance sorts ascending. +Alongside `fns.cosineDistance`, pgvector gives you `fns.cosineSimilarity`, and both take two vector expressions and return a `float8`. The difference is which way you sort: order ascending when you sort by distance, and descending when you sort by similarity. ## How the pieces fit -One package, two registrations, one database: +The single package you installed shows up in your config file and again in your client, and both registrations end up at the same database: -## Capabilities +## What an extension declares [#capabilities] -Each extension names what it adds under a key like `pgvector.cosine`, but you never write these keys by hand. Registering the extension in the config records them in your contract, and registering it in `db.ts` provides them at runtime. +Every extension names the database features it adds under keys like `pgvector.cosine`, and you never write those keys by hand: registering the extension in the config is what records them in your contract, and registering it in `db.ts` is what makes them available while your app is running. -The point of this bookkeeping is to fail early: - -- If your contract needs an extension that `db.ts` does not register, creating the client fails immediately. A query never runs with the extension only partially registered. -- If the database itself cannot install the extension, `db init` or `db update` reports it before your app takes traffic. +Keeping those two registrations in step is what lets Prisma ORM tell you something is missing before a query goes wrong. If your contract needs an extension that `db.ts` does not register, creating the client fails immediately, so a query never runs with the extension registered in only one of the two places. If the database itself cannot install the extension, `db init` or `db update` reports it before your app takes traffic. ## Available extensions -Every extension, by Prisma and by the community, is listed in the [extension directory](https://www.prisma.io/extensions) with install commands and registration snippets. The table below is generated from the same registry. +Every extension, whether Prisma wrote it or someone in the community did, is listed in the [extension directory](https://www.prisma.io/extensions) with its install command and the registration snippets to copy. The table here is generated from that same registry. {/* extensions-catalog:start not-tag=middleware */} | Name | What it adds | Package | Databases | By | @@ -123,22 +120,22 @@ Every extension, by Prisma and by the community, is listed in the [extension dir | [zod-json](https://www.prisma.io/extensions/zod-json) | Typed JSON columns described and enforced by a zod schema. | `prisma-orm-extension-zod-json` | PostgreSQL | [Omar Dulaimi](https://github.com/omar-dulaimi) | {/* extensions-catalog:end */} -The database packages are extensions too: `@prisma/orm-postgres`, `@prisma/orm-sqlite`, and `@prisma/orm-mongo` plug the SQL and document families into the same core, which is how a community package can add another database. Experimental extensions work but their surface is still moving: ParadeDB supports the `key_field` index option only so far, and SQLite is published but not yet covered by a quickstart. Names link to each extension's directory page, which links on to the package README. Middleware is listed on [How middleware works](/orm/middleware/how-middleware-works). +Support for a database is itself an extension, which is why `@prisma/orm-postgres`, `@prisma/orm-sqlite`, and `@prisma/orm-mongo` are in the table: they connect SQL databases and document databases to the same core, and that is how a community package can add another database. The ones marked experimental work today, but the methods and options they give you are still changing, which is why ParadeDB supports only the `key_field` index option so far and SQLite is published without a quickstart to follow. Each name in the table links to that extension's directory page, and from there to the package README. Middleware wraps queries rather than adding a database feature, so it is listed on [How middleware works](/orm/middleware/how-middleware-works) instead. :::note[Supabase does not follow the recipe above] -Supabase ships no `/control` subpath, so import its pack from `@prisma/orm-extension-supabase/pack` instead and pass it to the same `ormConfig({ extensions: [supabasePack] })` call the recipe above uses. The client is where the wiring differs: build it with the `supabase()` factory from `@prisma/orm-extension-supabase/runtime` rather than `postgres()`. That factory is role-first (`asUser(jwt)`, `asAnon()`, `asServiceRole()`) and carries the JWT validation and RLS binding. See the [runnable example](https://github.com/prisma/orm/tree/main/examples/supabase) for the whole wiring. +Supabase ships no `/control` subpath, so import its pack from `@prisma/orm-extension-supabase/pack` instead, and pass that to the same `ormConfig({ extensions: [supabasePack] })` call step 2 uses. The client is where the real difference is, because you build it with the `supabase()` factory from `@prisma/orm-extension-supabase/runtime` rather than with `postgres()`. That factory is built around roles: you pick the role you want with `asUser(jwt)`, `asAnon()`, or `asServiceRole()`, and the factory is what validates the JWT and binds the client to that role for row-level security. The [runnable example](https://github.com/prisma/orm/tree/main/examples/supabase) shows the whole setup in one project. ::: -For a working project per extension, see the runnable examples: [pgvector](https://github.com/prisma/orm/tree/main/examples/prisma-8-demo), [PostGIS](https://github.com/prisma/orm/tree/main/examples/prisma-8-postgis-demo), [ParadeDB](https://github.com/prisma/orm/tree/main/examples/paradedb-demo), and [Supabase](https://github.com/prisma/orm/tree/main/examples/supabase). +If you would rather read a whole working project than a set of snippets, there is a runnable example for each of these extensions: [pgvector](https://github.com/prisma/orm/tree/main/examples/prisma-8-demo), [PostGIS](https://github.com/prisma/orm/tree/main/examples/prisma-8-postgis-demo), [ParadeDB](https://github.com/prisma/orm/tree/main/examples/paradedb-demo), and [Supabase](https://github.com/prisma/orm/tree/main/examples/supabase). -You can build an extension that does not exist yet. Extension packs are versioned npm packages with a documented layout; the [call for extension authors](https://www.prisma.io/blog/prisma-next-call-for-extension-authors) explains how to write and publish one. When it is on npm, [submit it to the directory](https://www.prisma.io/extensions/submit): the form validates the entry and opens the pull request for you. +If the extension you need does not exist yet, you can build it. An extension pack is a versioned npm package with a documented layout, and the [call for extension authors](https://www.prisma.io/blog/prisma-next-call-for-extension-authors) explains how to write and publish one. Once yours is on npm, [submit it to the directory](https://www.prisma.io/extensions/submit), where the form validates your entry and opens the pull request for you. ## See also - [Advanced queries](/orm/fundamentals/advanced-queries): the SQL query builder, where extension operations like `cosineDistance` appear -- [How middleware works](/orm/middleware/how-middleware-works) for wrapping queries rather than adding database capabilities +- [How middleware works](/orm/middleware/how-middleware-works) for wrapping queries rather than adding database features - [Quickstart with PostgreSQL](/prisma-orm/quickstart/postgresql) to set up a project to add extensions to - [Extensions overview](/orm/extensions) for the full catalog, including middleware - [Prisma ORM overview](/orm) for the contract-first model extensions plug into From 33fff09aeee51d508a263e295a8cc21e4d944cf8 Mon Sep 17 00:00:00 2001 From: willbot Date: Thu, 17 Sep 2026 11:09:02 +0200 Subject: [PATCH 04/10] docs(orm): extensions reader round 1 Two fresh readers with Prisma ORM 7 experience read one page each. The fixes define contract before first use, say how the database package and other extensions are registered differently, type the query vector as number[] and show how an embedding is written, explain the contract-space layout violation and what to commit, say that the PostgreSQL server must have pgvector available, name the RUNTIME.MISSING_EXTENSION_PACK error, and define control, runtime, and pack. The index page keeps no code. Co-Authored-By: Claude Fable 5.1 Signed-off-by: willbot Signed-off-by: Will Madden --- .../content/docs/orm/extensions/index.mdx | 20 ++++-- .../docs/orm/extensions/using-extensions.mdx | 69 ++++++++++++------- 2 files changed, 59 insertions(+), 30 deletions(-) diff --git a/apps/docs/content/docs/orm/extensions/index.mdx b/apps/docs/content/docs/orm/extensions/index.mdx index 982ccbcb9a5..0ec8355c9cf 100644 --- a/apps/docs/content/docs/orm/extensions/index.mdx +++ b/apps/docs/content/docs/orm/extensions/index.mdx @@ -3,16 +3,18 @@ title: Extensions description: 'Every package that plugs into Prisma ORM: database packages, column types, indexes, query operations, and middleware, by Prisma and the community.' url: /orm/extensions metaTitle: Prisma ORM extensions -metaDescription: 'The catalog of Prisma ORM extensions with a one-line summary of each, how to register one, and how to publish your own.' +metaDescription: 'The catalog of Prisma ORM extensions with a one-line summary of each, and where to go to install one or publish your own.' --- -An extension is an npm package that adds something Prisma ORM does not do on its own. The packages that add support for a database are extensions themselves, and so are column types such as vectors and geometries, index types, query operations, and the middleware that runs around each query. +An extension is an npm package that adds something Prisma ORM does not do on its own. The packages that add support for a database are extensions themselves, so there is no built-in list of database providers to choose from: you install `@prisma/orm-postgres` to use PostgreSQL. Column types such as vectors and geometries are extensions too, and so are index types, query operations, and middleware, which runs your own code before and after every query. -Installing the package is not the whole job, because you also register the extension where it is used. You register it in `prisma.config.ts` so that `prisma contract emit` can write `contract.json` and `contract.d.ts` from your contract and the migration commands can plan your migrations, and you register it again when you create the client so that your queries can use the new types and operations. [Using extensions](/orm/extensions/using-extensions) walks through both registrations with pgvector. Middleware is the exception, because you register it on the client only, and [How middleware works](/orm/middleware/how-middleware-works) covers that. +Your contract is the Prisma ORM 8 name for your schema: [`contract.prisma`](/orm/contract-authoring/the-data-contract) in a generated project, in place of `schema.prisma`. `npx prisma contract emit` writes `contract.json` and `contract.d.ts` from it, and those two files are what `db.ts` imports. + +Installing an extension package is not the whole job, because you register the extension in two places. You register it in `prisma.config.ts` first, so that the CLI can write those two files from your contract and plan your migrations. You register it again where you create the client, so that your queries can use the types and operations the extension adds. The database package is the one you build on: it provides `ormConfig(...)` for the config and `postgres(...)` for the client. Every other extension is passed to those two calls in their `extensions` array, so pgvector goes alongside your PostgreSQL package rather than replacing it. [Using extensions](/orm/extensions/using-extensions) shows both registrations, with the install command, for pgvector. Middleware is the exception, because you register it on the client only, and [How middleware works](/orm/middleware/how-middleware-works) covers that. ## Catalog -Every entry below comes from the registry behind the [extension directory](https://www.prisma.io/extensions), where you will also find the install command and the registration snippets for each one. +Every entry below comes from the registry behind the [extension directory](https://www.prisma.io/extensions), which also gives you the install command and the registration snippet for each one. {/* extensions-catalog:start */} | Name | What it adds | Package | Databases | By | @@ -31,13 +33,17 @@ Every entry below comes from the registry behind the [extension directory](https | [zod-json](https://www.prisma.io/extensions/zod-json) | Typed JSON columns described and enforced by a zod schema. | `prisma-orm-extension-zod-json` | PostgreSQL | [Omar Dulaimi](https://github.com/omar-dulaimi) | {/* extensions-catalog:end */} -An entry marked experimental works today, but its API can change between release candidates. +An entry marked experimental works today, but its API can change between releases. + +Where a row says contract authoring, the package adds column types and other declarations that you then write in your contract yourself, such as `pgvector.Vector(1536)` for a vector column. The role-bound clients in the Supabase row are client instances pinned to a particular PostgreSQL role, so the database applies that role's row-level security policies to whatever you query through them. ## Publish your own -An extension you write yourself is usually a versioned npm package with one entrypoint for each of the places it gets registered: a `control` entrypoint for the config and a `runtime` entrypoint for the client. Middleware is again the exception and ships a single entrypoint. Adding a whole database uses that same layout, so if that is what you are writing, `@prisma/orm-postgres` is the one to follow for a SQL database and `@prisma/orm-mongo` for a document database. The [call for extension authors](https://www.prisma.io/blog/prisma-next-call-for-extension-authors) describes the layout. To write middleware, follow [Authoring custom middleware](/orm/middleware/authoring-custom-middleware). +An extension you write yourself is a versioned npm package with one entrypoint for each of the two places it gets registered. `control` is the subpath the CLI imports from `prisma.config.ts` and `runtime` is the subpath your app imports in `db.ts`. They are subpath exports of the package, so whoever installs it writes them as `@prisma/orm-extension-pgvector/control` and `@prisma/orm-extension-pgvector/runtime`. Middleware is again the exception: it ships a single entrypoint rather than a `control` and a `runtime` one, because it is only ever registered on the client. The [call for extension authors](https://www.prisma.io/blog/prisma-next-call-for-extension-authors) describes the layout in full, and to write middleware, follow [Authoring custom middleware](/orm/middleware/authoring-custom-middleware). + +Adding a whole database uses that same layout, so if that is what you are writing, follow `@prisma/orm-postgres` for a SQL database and `@prisma/orm-mongo` for a document database. -Once your package is on npm, [submit it to the directory](https://www.prisma.io/extensions/submit). The form checks that your entry is valid and that the package really exists on npm, then opens a pull request for a maintainer to review. +Once your package is on npm, [submit it to the directory](https://www.prisma.io/extensions/submit). A maintainer reviews your entry before it appears in the catalog. ## See also diff --git a/apps/docs/content/docs/orm/extensions/using-extensions.mdx b/apps/docs/content/docs/orm/extensions/using-extensions.mdx index 41ec8b5ebee..cfdc9f5ca6a 100644 --- a/apps/docs/content/docs/orm/extensions/using-extensions.mdx +++ b/apps/docs/content/docs/orm/extensions/using-extensions.mdx @@ -6,11 +6,13 @@ metaTitle: Using extensions in Prisma ORM metaDescription: 'Add an extension to a Prisma ORM project, from install to first query, and browse the catalog of available extensions such as pgvector, PostGIS, and ParadeDB.' --- -An extension is a package that teaches Prisma ORM a database feature it does not know out of the box. Installing one gives you new column types to declare in your schema, new query operations to call, and new index types, along with the migrations that install the feature in the database itself. Vector search, geospatial data, full-text search, typed JSON, and provider-specific integrations all reach your project this way. +An extension is a package that teaches Prisma ORM a database feature it does not know out of the box. Installing one gives you new column types to declare, new query operations to call, and in some extensions new index types, along with the migrations that install the feature in the database itself. Vector search, geospatial data, full-text search, typed JSON, and provider-specific integrations all reach your project this way. Reach for an extension when your application needs one of those database features and you still want everything Prisma ORM gives you for the rest of your data: typed schema declarations, generated TypeScript, migration support, and query helpers. -Adding an extension means installing its package and then registering it in two places, your config file and your client. The steps here use pgvector, the vector search extension, as the example. Supabase is set up differently, so if that is the extension you are adding, read [the note under the catalog](#available-extensions) first. +Your contract is the Prisma ORM 8 name for your schema: `contract.prisma` in a generated project, in place of `schema.prisma`. `npx prisma contract emit` writes `contract.json` and `contract.d.ts` from it, and those two files are what `db.ts` imports. [The data contract](/orm/contract-authoring/the-data-contract) covers what goes in it. + +Adding an extension means installing its package and then registering it in two places, your config file and your client. These steps use pgvector, the vector search extension, as the example, and they assume a PostgreSQL project from the [quickstart](/prisma-orm/quickstart/postgresql), so `@prisma/orm-postgres` is already installed and the extension package is added next to it. Supabase is set up differently, so if that is the extension you are adding, read [the note under the catalog](#available-extensions) first. ## 1. Install the package @@ -20,7 +22,7 @@ npm install @prisma/orm-extension-pgvector ## 2. Register it in the config -Your config file is what the `prisma` command line tools read, so the registration here is the one they use: it is how `prisma contract emit` knows about the extension's types when it writes your contract, and how the migration commands know about the migrations the extension ships. +Add the extension to the `extensions` array in your config file. The `prisma` command line tools read `prisma.config.ts` on every run, so listing the extension here is what lets `npx prisma contract emit` generate types for the extension's columns, and what lets the migration commands run the SQL the extension ships. ```ts title="prisma.config.ts" import { definePrismaConfig } from 'prisma/config'; @@ -38,9 +40,11 @@ export default definePrismaConfig({ }); ``` +The database package is the one you build on: `@prisma/orm-postgres` is what provides `ormConfig(...)` for the config and `postgres(...)` for the client. Every other extension, pgvector here, is passed to those two calls in their `extensions` array. Extensions publish separate entry points for the two, and `control` is the one the command line tools import, which is why the config file imports `@prisma/orm-extension-pgvector/control`. + ## 3. Register it on the client -The second registration is the one your application uses while it is running: passing the extension to `postgres(...)` is what adds the extension's query operations and value types to the `db` client you write queries against. +Add the extension to the `extensions` array on the client as well, which is what gives the `db` object you write queries against the extension's query operations and value types. Your application imports the `runtime` entry point rather than `control`. ```ts title="src/prisma/db.ts" import pgvector from '@prisma/orm-extension-pgvector/runtime'; @@ -55,9 +59,11 @@ export const db = postgres({ }); ``` +A project made with `npm create prisma@latest` already has `contract.json` and `contract.d.ts` from the scaffold, so these two imports resolve as soon as you write them. They describe your schema as it was before you edit it, and `npx prisma contract emit` in step 5 brings them up to date. + ## 4. Use the new type in your schema -Now that pgvector is registered in the config, its types are available in your contract, so you can give a column a vector type. A vector column needs an explicit number of dimensions, so name the type once under `types` and then use that name on the model: +Now that pgvector is registered in the config, its types are available in your contract, so you can give a column a vector type. A vector column needs an explicit number of dimensions, so declare that dimension once as a named type in a `types` block, and then every column that uses the name gets the same dimension. The trailing `?` on `Embedding1536?` makes the column optional, exactly as `?` did in Prisma ORM 7. The `types` block is part of [PSL syntax](/orm/contract-authoring/psl-syntax). ```prisma title="src/prisma/contract.prisma" types { @@ -73,32 +79,47 @@ model Post { ## 5. Apply and query -Run `npx prisma contract emit` first, so that `contract.json` and `contract.d.ts` pick up the new column, and then run `npx prisma db init` on a new database, or `npx prisma db update` if the database already exists. You do not have to install the pgvector feature in PostgreSQL yourself, because the extension ships its own migration and this step runs `CREATE EXTENSION IF NOT EXISTS vector` for you. If `db init` reports a contract-space layout violation instead of succeeding, run `npx prisma migration plan` once. That writes the extension's baseline migration under `migrations//`, and `db init` goes through after that. With the column in your database, you can query it with the operations the extension adds: +Run `npx prisma contract emit` first, so that `contract.json` and `contract.d.ts` pick up the new column. Then run `npx prisma db init` on a new database, or `npx prisma db update` if the database already exists. Run `contract emit` again after every later edit to `contract.prisma`, because the migration commands read `contract.json` rather than the `.prisma` file. + +`db init` reports a contract-space layout violation when the extension's own migration has not been written to disk yet. Run `npx prisma migration plan` once to write it under `migrations/pgvector/`, then run `npx prisma db init` again. That directory is part of your project's migrations, so commit it with the rest of `migrations/`. + +The extension ships the migration that runs `CREATE EXTENSION IF NOT EXISTS vector`, so you do not write that yourself, but the PostgreSQL server has to have pgvector available for it to succeed. The local development server the quickstart sets up has it. A stock PostgreSQL image may not, and then the migration fails on that statement. + +With the column in your database, write an embedding into it. A vector value is a plain `number[]` with as many entries as the column's dimension, so a column typed `Embedding1536` takes an array of 1536 numbers, which is what an embedding model gives you. Prisma ORM does not produce the numbers, and `embed` in this example stands for whichever model you call: + +```ts title="src/prisma/write-post.ts" +const embedding: number[] = await embed('about cats'); +await db.orm.public.Post.create({ title: 'about cats', embedding }); +``` + +Searching for the rows closest to a query vector uses the operations the extension adds. The query vector is a `number[]` of the same length as the stored embeddings: ```ts title="src/prisma/similarity-search.ts" -const plan = db.sql.public.post - .select('id', 'title') - .select('distance', (f, fns) => fns.cosineDistance(f.embedding, queryVector)) - .orderBy((f, fns) => fns.cosineDistance(f.embedding, queryVector), { direction: 'asc' }) - .limit(10) - .build(); - -const similar = await db.runtime().query(plan); +export async function similaritySearch(queryVector: number[]) { + const plan = db.sql.public.post + .select('id', 'title') + .select('distance', (f, fns) => fns.cosineDistance(f.embedding, queryVector)) + .orderBy((f, fns) => fns.cosineDistance(f.embedding, queryVector), { direction: 'asc' }) + .limit(10) + .build(); + + return db.runtime().query(plan); +} ``` -Alongside `fns.cosineDistance`, pgvector gives you `fns.cosineSimilarity`, and both take two vector expressions and return a `float8`. The difference is which way you sort: order ascending when you sort by distance, and descending when you sort by similarity. +`db.sql.public.post` is the `post` table in the PostgreSQL schema `public`, addressed through the SQL query builder described in [Advanced queries](/orm/fundamentals/advanced-queries). `.build()` returns the query as a plain object, and `db.runtime().query(plan)` runs it and returns the rows. + +Alongside `fns.cosineDistance`, pgvector gives you `fns.cosineSimilarity`. Both take two vector expressions and return a `float8`, which you get as a `number`. The difference is which way you sort: order ascending when you sort by distance, because the closest row has the smallest distance, and descending when you sort by similarity. ## How the pieces fit -The single package you installed shows up in your config file and again in your client, and both registrations end up at the same database: +One installed package supplies both registrations, because `prisma.config.ts` imports its `control` entry point and `db.ts` imports its `runtime` entry point, and both describe the same database: -## What an extension declares [#capabilities] - -Every extension names the database features it adds under keys like `pgvector.cosine`, and you never write those keys by hand: registering the extension in the config is what records them in your contract, and registering it in `db.ts` is what makes them available while your app is running. +## When a registration is missing [#capabilities] -Keeping those two registrations in step is what lets Prisma ORM tell you something is missing before a query goes wrong. If your contract needs an extension that `db.ts` does not register, creating the client fails immediately, so a query never runs with the extension registered in only one of the two places. If the database itself cannot install the extension, `db init` or `db update` reports it before your app takes traffic. +If your contract uses an extension that `db.ts` does not register, creating the client throws an error whose `code` is `RUNTIME.MISSING_EXTENSION_PACK`, before any query runs. If the database cannot install the extension, `db init` or `db update` reports it. ## Available extensions @@ -120,17 +141,19 @@ Every extension, whether Prisma wrote it or someone in the community did, is lis | [zod-json](https://www.prisma.io/extensions/zod-json) | Typed JSON columns described and enforced by a zod schema. | `prisma-orm-extension-zod-json` | PostgreSQL | [Omar Dulaimi](https://github.com/omar-dulaimi) | {/* extensions-catalog:end */} -Support for a database is itself an extension, which is why `@prisma/orm-postgres`, `@prisma/orm-sqlite`, and `@prisma/orm-mongo` are in the table: they connect SQL databases and document databases to the same core, and that is how a community package can add another database. The ones marked experimental work today, but the methods and options they give you are still changing, which is why ParadeDB supports only the `key_field` index option so far and SQLite is published without a quickstart to follow. Each name in the table links to that extension's directory page, and from there to the package README. Middleware wraps queries rather than adding a database feature, so it is listed on [How middleware works](/orm/middleware/how-middleware-works) instead. +The packages that add support for a database are extensions themselves, which is why `@prisma/orm-postgres`, `@prisma/orm-sqlite`, and `@prisma/orm-mongo` are in the table. The database package is the one you build on: it provides `ormConfig(...)` for the config and `postgres(...)` for the client. Every other extension is passed to those two calls in their `extensions` array, so a project has one database package and any number of extensions listed next to it. + +The ones marked experimental work today, but their methods and options are still changing between releases. They are also incomplete in places: ParadeDB supports only the `key_field` index option so far, and SQLite is published without a quickstart to follow. Each name in the table links to that extension's directory page, and from there to the package README. Middleware wraps queries rather than adding a database feature, so it is listed on [How middleware works](/orm/middleware/how-middleware-works) instead. :::note[Supabase does not follow the recipe above] -Supabase ships no `/control` subpath, so import its pack from `@prisma/orm-extension-supabase/pack` instead, and pass that to the same `ormConfig({ extensions: [supabasePack] })` call step 2 uses. The client is where the real difference is, because you build it with the `supabase()` factory from `@prisma/orm-extension-supabase/runtime` rather than with `postgres()`. That factory is built around roles: you pick the role you want with `asUser(jwt)`, `asAnon()`, or `asServiceRole()`, and the factory is what validates the JWT and binds the client to that role for row-level security. The [runnable example](https://github.com/prisma/orm/tree/main/examples/supabase) shows the whole setup in one project. +Supabase publishes no `/control` entry point. The bundle the command line tools need is published at `@prisma/orm-extension-supabase/pack` instead, so import that and pass it to the same `ormConfig({ extensions: [supabasePack] })` call step 2 uses. The client is where the real difference is, because you build it with the `supabase()` factory from `@prisma/orm-extension-supabase/runtime` rather than with `postgres()`. That factory is built around roles: you pick the role you want with `asUser(jwt)`, `asAnon()`, or `asServiceRole()`, and the factory is what validates the JWT and binds the client to that role for row-level security. The [runnable example](https://github.com/prisma/orm/tree/main/examples/supabase) shows the whole setup in one project. ::: If you would rather read a whole working project than a set of snippets, there is a runnable example for each of these extensions: [pgvector](https://github.com/prisma/orm/tree/main/examples/prisma-8-demo), [PostGIS](https://github.com/prisma/orm/tree/main/examples/prisma-8-postgis-demo), [ParadeDB](https://github.com/prisma/orm/tree/main/examples/paradedb-demo), and [Supabase](https://github.com/prisma/orm/tree/main/examples/supabase). -If the extension you need does not exist yet, you can build it. An extension pack is a versioned npm package with a documented layout, and the [call for extension authors](https://www.prisma.io/blog/prisma-next-call-for-extension-authors) explains how to write and publish one. Once yours is on npm, [submit it to the directory](https://www.prisma.io/extensions/submit), where the form validates your entry and opens the pull request for you. +If the extension you need does not exist yet, you can build it. An extension is a versioned npm package with a documented layout, and the [call for extension authors](https://www.prisma.io/blog/prisma-next-call-for-extension-authors) explains how to write and publish one. Once yours is on npm, [submit it to the directory](https://www.prisma.io/extensions/submit), where the form validates your entry and opens the pull request for you. ## See also From c247c7c3c6d8a5c3729ac4e4c4b2cb6bd31fbabd Mon Sep 17 00:00:00 2001 From: willbot Date: Thu, 17 Sep 2026 11:16:30 +0200 Subject: [PATCH 05/10] docs(orm): extensions reader round 2 Second round of fresh readers. Step 5 is now a sequence: contract emit, migration plan, db init, with the layout-violation error named by code. The Supabase note has real config and client code from the source's example. Model versus table naming, the three namespaces on db, the query builder callback, and float8 are each explained once. The contract-authoring note no longer uses a pgvector example, MySQL's absence and the @prisma/client replacement are stated once, and the control and runtime split is explained once. Co-Authored-By: Claude Fable 5.1 Signed-off-by: willbot Signed-off-by: Will Madden --- .../content/docs/orm/extensions/index.mdx | 15 ++-- .../docs/orm/extensions/using-extensions.mdx | 70 ++++++++++++++----- 2 files changed, 61 insertions(+), 24 deletions(-) diff --git a/apps/docs/content/docs/orm/extensions/index.mdx b/apps/docs/content/docs/orm/extensions/index.mdx index 0ec8355c9cf..2c3d953f7c7 100644 --- a/apps/docs/content/docs/orm/extensions/index.mdx +++ b/apps/docs/content/docs/orm/extensions/index.mdx @@ -6,15 +6,15 @@ metaTitle: Prisma ORM extensions metaDescription: 'The catalog of Prisma ORM extensions with a one-line summary of each, and where to go to install one or publish your own.' --- -An extension is an npm package that adds something Prisma ORM does not do on its own. The packages that add support for a database are extensions themselves, so there is no built-in list of database providers to choose from: you install `@prisma/orm-postgres` to use PostgreSQL. Column types such as vectors and geometries are extensions too, and so are index types, query operations, and middleware, which runs your own code before and after every query. +An extension is an npm package that adds something Prisma ORM does not do on its own. Support for a database comes from an extension too, so you install `@prisma/orm-postgres` to use PostgreSQL, or `@prisma/orm-mongo` to use MongoDB. `@prisma/orm-postgres` takes the place of both `@prisma/client` and the `provider` line in `schema.prisma`, which is why there is no built-in list of database providers to choose from. Column types such as vectors and geometries are extensions too, and so are index types, query operations, and middleware, which runs your own code before and after every query. -Your contract is the Prisma ORM 8 name for your schema: [`contract.prisma`](/orm/contract-authoring/the-data-contract) in a generated project, in place of `schema.prisma`. `npx prisma contract emit` writes `contract.json` and `contract.d.ts` from it, and those two files are what `db.ts` imports. +Your contract is the Prisma ORM 8 name for your schema: [`contract.prisma`](/orm/contract-authoring/the-data-contract) in a project made with `npm create prisma@latest`, in place of `schema.prisma`. `npx prisma contract emit` writes `contract.json` and `contract.d.ts` from it, and those two files are what `db.ts` imports. `db.ts` is the file where you create the client, at `src/prisma/db.ts` in that same project, and the rest of your app imports the client from there. -Installing an extension package is not the whole job, because you register the extension in two places. You register it in `prisma.config.ts` first, so that the CLI can write those two files from your contract and plan your migrations. You register it again where you create the client, so that your queries can use the types and operations the extension adds. The database package is the one you build on: it provides `ormConfig(...)` for the config and `postgres(...)` for the client. Every other extension is passed to those two calls in their `extensions` array, so pgvector goes alongside your PostgreSQL package rather than replacing it. [Using extensions](/orm/extensions/using-extensions) shows both registrations, with the install command, for pgvector. Middleware is the exception, because you register it on the client only, and [How middleware works](/orm/middleware/how-middleware-works) covers that. +Installing an extension package is not the whole job, because you register the extension in two places. You register it in `prisma.config.ts` first, so that the CLI can write `contract.json` and `contract.d.ts` from your contract and write the migration files the extension needs. You register it again in `db.ts`, so that your queries can use the types and operations the extension adds. The database package is the one you build on: it provides `ormConfig(...)` for the config file and `postgres(...)` for the client. Both of those calls take an `extensions` array, and that is where every other extension goes. [Using extensions](/orm/extensions/using-extensions) shows both registrations, with the install command, for pgvector. Middleware is the exception, because you register it on the client only, and [How middleware works](/orm/middleware/how-middleware-works) covers that. ## Catalog -Every entry below comes from the registry behind the [extension directory](https://www.prisma.io/extensions), which also gives you the install command and the registration snippet for each one. +These entries come from the registry behind the [extension directory](https://www.prisma.io/extensions), which also gives you the install command and the registration snippet for each one. There is no MySQL package, and [Supported databases](/orm/supported-databases) says what is planned. {/* extensions-catalog:start */} | Name | What it adds | Package | Databases | By | @@ -35,13 +35,13 @@ Every entry below comes from the registry behind the [extension directory](https An entry marked experimental works today, but its API can change between releases. -Where a row says contract authoring, the package adds column types and other declarations that you then write in your contract yourself, such as `pgvector.Vector(1536)` for a vector column. The role-bound clients in the Supabase row are client instances pinned to a particular PostgreSQL role, so the database applies that role's row-level security policies to whatever you query through them. +Where a row says contract authoring, the package is the one that lets you write your contract for that database at all: it provides the types and attributes your `contract.prisma` is written in. The role-bound clients in the Supabase row are client instances pinned to a particular PostgreSQL role, so the database applies that role's row-level security policies to whatever you query through them. ## Publish your own -An extension you write yourself is a versioned npm package with one entrypoint for each of the two places it gets registered. `control` is the subpath the CLI imports from `prisma.config.ts` and `runtime` is the subpath your app imports in `db.ts`. They are subpath exports of the package, so whoever installs it writes them as `@prisma/orm-extension-pgvector/control` and `@prisma/orm-extension-pgvector/runtime`. Middleware is again the exception: it ships a single entrypoint rather than a `control` and a `runtime` one, because it is only ever registered on the client. The [call for extension authors](https://www.prisma.io/blog/prisma-next-call-for-extension-authors) describes the layout in full, and to write middleware, follow [Authoring custom middleware](/orm/middleware/authoring-custom-middleware). +An extension you write yourself is an npm package with one entrypoint for each of the two places it gets registered. `control` is the subpath the CLI imports from `prisma.config.ts` and `runtime` is the subpath your app imports in `db.ts`, and they are imported as `@prisma/orm-extension-pgvector/control` and `@prisma/orm-extension-pgvector/runtime`. Middleware is again the exception: it ships a single entrypoint rather than a `control` and a `runtime` one, because it is only ever registered on the client. The [call for extension authors](https://www.prisma.io/blog/prisma-next-call-for-extension-authors) describes the layout in full, and to write middleware, follow [Authoring custom middleware](/orm/middleware/authoring-custom-middleware). -Adding a whole database uses that same layout, so if that is what you are writing, follow `@prisma/orm-postgres` for a SQL database and `@prisma/orm-mongo` for a document database. +Adding a whole database uses that same layout, so if that is what you are writing, use `@prisma/orm-postgres` as the model, in `packages/3-extensions/postgres` of the [prisma/orm repository](https://github.com/prisma/orm), and `@prisma/orm-mongo` in `packages/3-extensions/mongo` for a document database. Once your package is on npm, [submit it to the directory](https://www.prisma.io/extensions/submit). A maintainer reviews your entry before it appears in the catalog. @@ -49,3 +49,4 @@ Once your package is on npm, [submit it to the directory](https://www.prisma.io/ - [Using extensions](/orm/extensions/using-extensions): install an extension and run your first query with it - [Supported databases](/orm/supported-databases): which databases Prisma ORM ships and what is planned +- [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7): what changes in a project that already uses `@prisma/client` and `schema.prisma` diff --git a/apps/docs/content/docs/orm/extensions/using-extensions.mdx b/apps/docs/content/docs/orm/extensions/using-extensions.mdx index cfdc9f5ca6a..45e1dabc74e 100644 --- a/apps/docs/content/docs/orm/extensions/using-extensions.mdx +++ b/apps/docs/content/docs/orm/extensions/using-extensions.mdx @@ -6,11 +6,11 @@ metaTitle: Using extensions in Prisma ORM metaDescription: 'Add an extension to a Prisma ORM project, from install to first query, and browse the catalog of available extensions such as pgvector, PostGIS, and ParadeDB.' --- -An extension is a package that teaches Prisma ORM a database feature it does not know out of the box. Installing one gives you new column types to declare, new query operations to call, and in some extensions new index types, along with the migrations that install the feature in the database itself. Vector search, geospatial data, full-text search, typed JSON, and provider-specific integrations all reach your project this way. +An extension is a package that teaches Prisma ORM a database feature it does not know out of the box. Installing one gives you new column types to declare and new query operations to call, along with the migrations that install the feature in the database itself. Vector search, geospatial data, full-text search, typed JSON, and provider-specific integrations all reach your project this way. Reach for an extension when your application needs one of those database features and you still want everything Prisma ORM gives you for the rest of your data: typed schema declarations, generated TypeScript, migration support, and query helpers. -Your contract is the Prisma ORM 8 name for your schema: `contract.prisma` in a generated project, in place of `schema.prisma`. `npx prisma contract emit` writes `contract.json` and `contract.d.ts` from it, and those two files are what `db.ts` imports. [The data contract](/orm/contract-authoring/the-data-contract) covers what goes in it. +Your contract is the Prisma ORM 8 name for your schema: `contract.prisma` in a project made with `npm create prisma@latest`, in place of `schema.prisma`. It is the same file under a new name, and you still describe your models in it. `npx prisma contract emit` writes `contract.json` and `contract.d.ts` from it, and those two files are what `db.ts` imports. [The data contract](/orm/contract-authoring/the-data-contract) covers what goes in it. Adding an extension means installing its package and then registering it in two places, your config file and your client. These steps use pgvector, the vector search extension, as the example, and they assume a PostgreSQL project from the [quickstart](/prisma-orm/quickstart/postgresql), so `@prisma/orm-postgres` is already installed and the extension package is added next to it. Supabase is set up differently, so if that is the extension you are adding, read [the note under the catalog](#available-extensions) first. @@ -22,7 +22,7 @@ npm install @prisma/orm-extension-pgvector ## 2. Register it in the config -Add the extension to the `extensions` array in your config file. The `prisma` command line tools read `prisma.config.ts` on every run, so listing the extension here is what lets `npx prisma contract emit` generate types for the extension's columns, and what lets the migration commands run the SQL the extension ships. +Add the extension to the `extensions` array in your config file. ```ts title="prisma.config.ts" import { definePrismaConfig } from 'prisma/config'; @@ -40,11 +40,13 @@ export default definePrismaConfig({ }); ``` -The database package is the one you build on: `@prisma/orm-postgres` is what provides `ormConfig(...)` for the config and `postgres(...)` for the client. Every other extension, pgvector here, is passed to those two calls in their `extensions` array. Extensions publish separate entry points for the two, and `control` is the one the command line tools import, which is why the config file imports `@prisma/orm-extension-pgvector/control`. +The database package is the one you build on: `@prisma/orm-postgres` is what provides `ormConfig(...)` for the config and `postgres(...)` for the client. Every other extension, pgvector here, is passed to those two calls in their `extensions` array. `ormConfig` is a rename: `@prisma/orm-postgres` exports that function as `defineConfig`, and the import above renames it so that it is not mistaken for `definePrismaConfig` from `prisma/config`. The name is yours to choose. + +An extension package has two entry points, and which one you import depends on which file you are in. `control` is the entry point the `prisma` command line tools read, so that is the one `prisma.config.ts` imports. `runtime` is the entry point your application code reads, so that is the one `db.ts` imports in the next step. ## 3. Register it on the client -Add the extension to the `extensions` array on the client as well, which is what gives the `db` object you write queries against the extension's query operations and value types. Your application imports the `runtime` entry point rather than `control`. +Add the extension to the `extensions` array on the client as well, which is what gives the `db` object you write queries against the extension's query operations and value types. ```ts title="src/prisma/db.ts" import pgvector from '@prisma/orm-extension-pgvector/runtime'; @@ -59,7 +61,7 @@ export const db = postgres({ }); ``` -A project made with `npm create prisma@latest` already has `contract.json` and `contract.d.ts` from the scaffold, so these two imports resolve as soon as you write them. They describe your schema as it was before you edit it, and `npx prisma contract emit` in step 5 brings them up to date. +The `'./contract.d'` in that import is not a typo: it is how you import `contract.d.ts`, one of the two files `npx prisma contract emit` writes. ## 4. Use the new type in your schema @@ -79,11 +81,13 @@ model Post { ## 5. Apply and query -Run `npx prisma contract emit` first, so that `contract.json` and `contract.d.ts` pick up the new column. Then run `npx prisma db init` on a new database, or `npx prisma db update` if the database already exists. Run `contract emit` again after every later edit to `contract.prisma`, because the migration commands read `contract.json` rather than the `.prisma` file. +Getting the new column from your contract into your database takes these commands, in this order: -`db init` reports a contract-space layout violation when the extension's own migration has not been written to disk yet. Run `npx prisma migration plan` once to write it under `migrations/pgvector/`, then run `npx prisma db init` again. That directory is part of your project's migrations, so commit it with the rest of `migrations/`. +1. `npx prisma contract emit`, so that `contract.json` and `contract.d.ts` pick up the new column. Run it again after every later change to `contract.prisma`, because the migration commands read `contract.json` rather than the `.prisma` file. +2. `npx prisma migration plan`, which writes the extension's own migration under `migrations/pgvector/`. That directory is part of your project's migrations, so commit it with the rest of `migrations/`. Until it exists, the next command stops with an error whose `code` is `MIGRATION.CONTRACT_SPACE_LAYOUT_VIOLATION`. +3. `npx prisma db init` on a new database, or `npx prisma db update` on one that already has tables. This runs the extension's migration, which is the `CREATE EXTENSION IF NOT EXISTS vector` statement, and creates the `post` table. -The extension ships the migration that runs `CREATE EXTENSION IF NOT EXISTS vector`, so you do not write that yourself, but the PostgreSQL server has to have pgvector available for it to succeed. The local development server the quickstart sets up has it. A stock PostgreSQL image may not, and then the migration fails on that statement. +You never write that `CREATE EXTENSION` statement yourself, but the PostgreSQL server has to have pgvector available for it to succeed. The local development server the quickstart sets up has it. A stock PostgreSQL image may not, and then the migration fails on that statement. With the column in your database, write an embedding into it. A vector value is a plain `number[]` with as many entries as the column's dimension, so a column typed `Embedding1536` takes an array of 1536 numbers, which is what an embedding model gives you. Prisma ORM does not produce the numbers, and `embed` in this example stands for whichever model you call: @@ -92,7 +96,7 @@ const embedding: number[] = await embed('about cats'); await db.orm.public.Post.create({ title: 'about cats', embedding }); ``` -Searching for the rows closest to a query vector uses the operations the extension adds. The query vector is a `number[]` of the same length as the stored embeddings: +Searching for the rows closest to a query vector uses the operations the extension adds, and you write that search with the SQL query builder rather than with `db.orm`. `db.orm.public.Post` is the model, so it uses the name from your contract, and `db.sql.public.post` is the table, so it uses the name as it is in the database. `db.orm` is the ORM API, `db.sql` is the [SQL query builder](/orm/fundamentals/advanced-queries), and `db.runtime()` is what runs a built query. The query vector is a `number[]` of the same length as the stored embeddings: ```ts title="src/prisma/similarity-search.ts" export async function similaritySearch(queryVector: number[]) { @@ -107,19 +111,19 @@ export async function similaritySearch(queryVector: number[]) { } ``` -`db.sql.public.post` is the `post` table in the PostgreSQL schema `public`, addressed through the SQL query builder described in [Advanced queries](/orm/fundamentals/advanced-queries). `.build()` returns the query as a plain object, and `db.runtime().query(plan)` runs it and returns the rows. +In the callback, `f` holds the row's columns and `fns` holds the functions you can call on them, including the ones pgvector adds. `.select('distance', ...)` names the computed column `distance`. `.build()` returns the query as a plain object, and `db.runtime().query(plan)` runs it and returns the rows. -Alongside `fns.cosineDistance`, pgvector gives you `fns.cosineSimilarity`. Both take two vector expressions and return a `float8`, which you get as a `number`. The difference is which way you sort: order ascending when you sort by distance, because the closest row has the smallest distance, and descending when you sort by similarity. +Alongside `fns.cosineDistance`, pgvector gives you `fns.cosineSimilarity`. Both take two vectors and return a PostgreSQL `float8`, which you get as a `number`. The difference is which way you sort: order ascending when you sort by distance, because the closest row has the smallest distance, and descending when you sort by similarity. ## How the pieces fit -One installed package supplies both registrations, because `prisma.config.ts` imports its `control` entry point and `db.ts` imports its `runtime` entry point, and both describe the same database: +One installed package supplies both registrations, the one in `prisma.config.ts` and the one in `db.ts`: ## When a registration is missing [#capabilities] -If your contract uses an extension that `db.ts` does not register, creating the client throws an error whose `code` is `RUNTIME.MISSING_EXTENSION_PACK`, before any query runs. If the database cannot install the extension, `db init` or `db update` reports it. +When your contract uses an extension and `db.ts` does not list it in `extensions`, `postgres(...)` throws an error whose `code` is `RUNTIME.MISSING_EXTENSION_PACK` before any query runs. Add the extension's `runtime` import to `db.ts` to fix it. If the database cannot install the extension, `db init` or `db update` reports it. ## Available extensions @@ -141,13 +145,45 @@ Every extension, whether Prisma wrote it or someone in the community did, is lis | [zod-json](https://www.prisma.io/extensions/zod-json) | Typed JSON columns described and enforced by a zod schema. | `prisma-orm-extension-zod-json` | PostgreSQL | [Omar Dulaimi](https://github.com/omar-dulaimi) | {/* extensions-catalog:end */} -The packages that add support for a database are extensions themselves, which is why `@prisma/orm-postgres`, `@prisma/orm-sqlite`, and `@prisma/orm-mongo` are in the table. The database package is the one you build on: it provides `ormConfig(...)` for the config and `postgres(...)` for the client. Every other extension is passed to those two calls in their `extensions` array, so a project has one database package and any number of extensions listed next to it. +The packages that add support for a database are extensions themselves, which is why `@prisma/orm-postgres`, `@prisma/orm-sqlite`, and `@prisma/orm-mongo` are in the table. A project has one database package and any number of other extensions listed next to it in the two `extensions` arrays. -The ones marked experimental work today, but their methods and options are still changing between releases. They are also incomplete in places: ParadeDB supports only the `key_field` index option so far, and SQLite is published without a quickstart to follow. Each name in the table links to that extension's directory page, and from there to the package README. Middleware wraps queries rather than adding a database feature, so it is listed on [How middleware works](/orm/middleware/how-middleware-works) instead. +The ones marked experimental work today, but their methods and options are still changing between releases. They are also incomplete in places: ParadeDB's full-text index takes only one option so far, `key_field`, and SQLite is published without a quickstart to follow. Each name in the table links to that extension's directory page, and from there to the package README. Middleware wraps queries rather than adding a database feature, so it is listed on [How middleware works](/orm/middleware/how-middleware-works) instead. :::note[Supabase does not follow the recipe above] -Supabase publishes no `/control` entry point. The bundle the command line tools need is published at `@prisma/orm-extension-supabase/pack` instead, so import that and pass it to the same `ormConfig({ extensions: [supabasePack] })` call step 2 uses. The client is where the real difference is, because you build it with the `supabase()` factory from `@prisma/orm-extension-supabase/runtime` rather than with `postgres()`. That factory is built around roles: you pick the role you want with `asUser(jwt)`, `asAnon()`, or `asServiceRole()`, and the factory is what validates the JWT and binds the client to that role for row-level security. The [runnable example](https://github.com/prisma/orm/tree/main/examples/supabase) shows the whole setup in one project. +Supabase publishes no `control` entry point. What the command line tools read is published at `@prisma/orm-extension-supabase/pack` instead, so the config imports that and passes it to the same `ormConfig(...)` call step 2 uses: + +```ts title="prisma.config.ts" +import { definePrismaConfig } from 'prisma/config'; +import supabasePack from '@prisma/orm-extension-supabase/pack'; +import { defineConfig as ormConfig } from '@prisma/orm-postgres/config'; + +export default definePrismaConfig({ + orm: ormConfig({ + contract: './src/prisma/contract.prisma', + extensions: [supabasePack], + db: { + connection: process.env['DATABASE_URL']!, + }, + }), +}); +``` + +The client is where the real difference is, because you build it with `supabase()` from `@prisma/orm-extension-supabase/runtime` rather than with `postgres()`, and it takes the secret it needs to check JWTs: + +```ts title="src/prisma/db.ts" +import { supabase } from '@prisma/orm-extension-supabase/runtime'; +import type { Contract } from './contract.d'; +import contractJson from './contract.json' with { type: 'json' }; + +export const db = supabase({ + contractJson, + url: process.env['DATABASE_URL']!, + jwtSecret: process.env['SUPABASE_JWT_SECRET']!, +}); +``` + +`db.asUser(jwt)` gives you a client that runs as that user, `db.asAnon()` one that runs as the anonymous role, and `db.asServiceRole()` one that bypasses row-level security, and each of them has the same `orm` and `sql` you use on `db`. The [runnable example](https://github.com/prisma/orm/tree/main/examples/supabase) shows the whole setup in one project. ::: From 6e91540075eb29c55a8ce6350caba821897622ed Mon Sep 17 00:00:00 2001 From: willbot Date: Thu, 17 Sep 2026 11:22:19 +0200 Subject: [PATCH 06/10] docs(orm): extensions reader round 3 Third round of fresh readers, cutting: the index page is a catalog with short pointers again, the entry-point rule now names /control, /config, and /pack as the packages actually publish them, public is explained once, the result rows are named, the source of the pgvector.Vector type is stated with a PostGIS example, and the contract-authoring note and repository paths are gone. Co-Authored-By: Claude Fable 5.1 Signed-off-by: willbot Signed-off-by: Will Madden --- .../content/docs/orm/extensions/index.mdx | 16 ++++----- .../docs/orm/extensions/using-extensions.mdx | 36 ++++++++++--------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/apps/docs/content/docs/orm/extensions/index.mdx b/apps/docs/content/docs/orm/extensions/index.mdx index 2c3d953f7c7..a7884f77864 100644 --- a/apps/docs/content/docs/orm/extensions/index.mdx +++ b/apps/docs/content/docs/orm/extensions/index.mdx @@ -6,15 +6,15 @@ metaTitle: Prisma ORM extensions metaDescription: 'The catalog of Prisma ORM extensions with a one-line summary of each, and where to go to install one or publish your own.' --- -An extension is an npm package that adds something Prisma ORM does not do on its own. Support for a database comes from an extension too, so you install `@prisma/orm-postgres` to use PostgreSQL, or `@prisma/orm-mongo` to use MongoDB. `@prisma/orm-postgres` takes the place of both `@prisma/client` and the `provider` line in `schema.prisma`, which is why there is no built-in list of database providers to choose from. Column types such as vectors and geometries are extensions too, and so are index types, query operations, and middleware, which runs your own code before and after every query. +An extension is an npm package that adds something Prisma ORM does not do on its own: a column type such as a vector or a geometry, an index type, query operations (new functions you can call in a query, such as pgvector's `cosineDistance`), or middleware, which runs your own code before and after every query. Support for a database comes from an extension too, so you install `@prisma/orm-postgres` to use PostgreSQL, or `@prisma/orm-mongo` to use MongoDB. -Your contract is the Prisma ORM 8 name for your schema: [`contract.prisma`](/orm/contract-authoring/the-data-contract) in a project made with `npm create prisma@latest`, in place of `schema.prisma`. `npx prisma contract emit` writes `contract.json` and `contract.d.ts` from it, and those two files are what `db.ts` imports. `db.ts` is the file where you create the client, at `src/prisma/db.ts` in that same project, and the rest of your app imports the client from there. +Installing the package is not the whole job, because you register an extension in two places: in `prisma.config.ts`, so that the command line tools know about it, and in `db.ts`, where you create the client, so that your queries can use what it adds. The database package is the one whose functions you call, `ormConfig(...)` and `postgres(...)`, and every other extension goes in the `extensions` array of each. [Using extensions](/orm/extensions/using-extensions) shows both registrations, with the install command, for pgvector. Middleware is the exception, because you register it on the client only, and [How middleware works](/orm/middleware/how-middleware-works) covers that. -Installing an extension package is not the whole job, because you register the extension in two places. You register it in `prisma.config.ts` first, so that the CLI can write `contract.json` and `contract.d.ts` from your contract and write the migration files the extension needs. You register it again in `db.ts`, so that your queries can use the types and operations the extension adds. The database package is the one you build on: it provides `ormConfig(...)` for the config file and `postgres(...)` for the client. Both of those calls take an `extensions` array, and that is where every other extension goes. [Using extensions](/orm/extensions/using-extensions) shows both registrations, with the install command, for pgvector. Middleware is the exception, because you register it on the client only, and [How middleware works](/orm/middleware/how-middleware-works) covers that. +Your contract is what Prisma ORM 8 calls your schema, written in [`contract.prisma`](/orm/contract-authoring/the-data-contract). If you have a Prisma ORM 7 project, [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7) says what replaced `schema.prisma`, `@prisma/client`, and `$use`. ## Catalog -These entries come from the registry behind the [extension directory](https://www.prisma.io/extensions), which also gives you the install command and the registration snippet for each one. There is no MySQL package, and [Supported databases](/orm/supported-databases) says what is planned. +The [extension directory](https://www.prisma.io/extensions) has the install command and the registration snippet for each entry below. There is no MySQL package, and [Supported databases](/orm/supported-databases) says what is planned. {/* extensions-catalog:start */} | Name | What it adds | Package | Databases | By | @@ -35,15 +35,13 @@ These entries come from the registry behind the [extension directory](https://ww An entry marked experimental works today, but its API can change between releases. -Where a row says contract authoring, the package is the one that lets you write your contract for that database at all: it provides the types and attributes your `contract.prisma` is written in. The role-bound clients in the Supabase row are client instances pinned to a particular PostgreSQL role, so the database applies that role's row-level security policies to whatever you query through them. +The role-bound clients in the Supabase row are client instances pinned to a particular PostgreSQL role, so the database applies that role's row-level security policies to whatever you query through them. ## Publish your own -An extension you write yourself is an npm package with one entrypoint for each of the two places it gets registered. `control` is the subpath the CLI imports from `prisma.config.ts` and `runtime` is the subpath your app imports in `db.ts`, and they are imported as `@prisma/orm-extension-pgvector/control` and `@prisma/orm-extension-pgvector/runtime`. Middleware is again the exception: it ships a single entrypoint rather than a `control` and a `runtime` one, because it is only ever registered on the client. The [call for extension authors](https://www.prisma.io/blog/prisma-next-call-for-extension-authors) describes the layout in full, and to write middleware, follow [Authoring custom middleware](/orm/middleware/authoring-custom-middleware). +An extension you write yourself is an npm package with one entry point for the command line tools and one for your application. For pgvector they are `@prisma/orm-extension-pgvector/control` and `@prisma/orm-extension-pgvector/runtime`. The database package uses `/config` and `/runtime` for the same two jobs, and Supabase publishes its command line side as `/pack`. The extension's directory page tells you which to import. Middleware ships a single entry point instead, because it is only ever registered on the client. -Adding a whole database uses that same layout, so if that is what you are writing, use `@prisma/orm-postgres` as the model, in `packages/3-extensions/postgres` of the [prisma/orm repository](https://github.com/prisma/orm), and `@prisma/orm-mongo` in `packages/3-extensions/mongo` for a document database. - -Once your package is on npm, [submit it to the directory](https://www.prisma.io/extensions/submit). A maintainer reviews your entry before it appears in the catalog. +The [call for extension authors](https://www.prisma.io/blog/prisma-next-call-for-extension-authors) describes the layout in full, and to write middleware, follow [Authoring custom middleware](/orm/middleware/authoring-custom-middleware). If you are adding a whole database, `@prisma/orm-postgres` is the one to copy for a SQL database and `@prisma/orm-mongo` for a document database. Once your package is on npm, [submit it to the directory](https://www.prisma.io/extensions/submit), and a maintainer reviews your entry before it appears in the catalog. ## See also diff --git a/apps/docs/content/docs/orm/extensions/using-extensions.mdx b/apps/docs/content/docs/orm/extensions/using-extensions.mdx index 45e1dabc74e..3d86f201821 100644 --- a/apps/docs/content/docs/orm/extensions/using-extensions.mdx +++ b/apps/docs/content/docs/orm/extensions/using-extensions.mdx @@ -10,7 +10,7 @@ An extension is a package that teaches Prisma ORM a database feature it does not Reach for an extension when your application needs one of those database features and you still want everything Prisma ORM gives you for the rest of your data: typed schema declarations, generated TypeScript, migration support, and query helpers. -Your contract is the Prisma ORM 8 name for your schema: `contract.prisma` in a project made with `npm create prisma@latest`, in place of `schema.prisma`. It is the same file under a new name, and you still describe your models in it. `npx prisma contract emit` writes `contract.json` and `contract.d.ts` from it, and those two files are what `db.ts` imports. [The data contract](/orm/contract-authoring/the-data-contract) covers what goes in it. +Your contract is what Prisma ORM 8 calls your schema. In a project made with `npm create prisma@latest` it is `src/prisma/contract.prisma`, in place of `schema.prisma`, and you still describe your models in it. `npx prisma contract emit` writes `contract.json` and `contract.d.ts` from it, and the file where you create the client (`src/prisma/db.ts`) imports those two rather than the `.prisma` file. [The data contract](/orm/contract-authoring/the-data-contract) covers what goes in it. Adding an extension means installing its package and then registering it in two places, your config file and your client. These steps use pgvector, the vector search extension, as the example, and they assume a PostgreSQL project from the [quickstart](/prisma-orm/quickstart/postgresql), so `@prisma/orm-postgres` is already installed and the extension package is added next to it. Supabase is set up differently, so if that is the extension you are adding, read [the note under the catalog](#available-extensions) first. @@ -22,7 +22,7 @@ npm install @prisma/orm-extension-pgvector ## 2. Register it in the config -Add the extension to the `extensions` array in your config file. +A project made with `npm create prisma@latest` already has `prisma.config.ts`, so open it and add the extension to the `extensions` array. ```ts title="prisma.config.ts" import { definePrismaConfig } from 'prisma/config'; @@ -40,9 +40,9 @@ export default definePrismaConfig({ }); ``` -The database package is the one you build on: `@prisma/orm-postgres` is what provides `ormConfig(...)` for the config and `postgres(...)` for the client. Every other extension, pgvector here, is passed to those two calls in their `extensions` array. `ormConfig` is a rename: `@prisma/orm-postgres` exports that function as `defineConfig`, and the import above renames it so that it is not mistaken for `definePrismaConfig` from `prisma/config`. The name is yours to choose. +The database package is the one whose functions you call, `ormConfig(...)` and `postgres(...)`, and every other extension goes in the `extensions` array of each. The example renames `defineConfig` to `ormConfig` so it is not confused with `definePrismaConfig`, and you can keep the original name. -An extension package has two entry points, and which one you import depends on which file you are in. `control` is the entry point the `prisma` command line tools read, so that is the one `prisma.config.ts` imports. `runtime` is the entry point your application code reads, so that is the one `db.ts` imports in the next step. +An extension has one entry point for the command line tools and one for your application. For pgvector they are `@prisma/orm-extension-pgvector/control` and `@prisma/orm-extension-pgvector/runtime`. The database package uses `/config` and `/runtime` for the same two jobs, and Supabase publishes its command-line side as `/pack`. The extension's directory page tells you which to import. ## 3. Register it on the client @@ -63,10 +63,12 @@ export const db = postgres({ The `'./contract.d'` in that import is not a typo: it is how you import `contract.d.ts`, one of the two files `npx prisma contract emit` writes. -## 4. Use the new type in your schema +## 4. Use the new type in your contract [#4-use-the-new-type-in-your-schema] Now that pgvector is registered in the config, its types are available in your contract, so you can give a column a vector type. A vector column needs an explicit number of dimensions, so declare that dimension once as a named type in a `types` block, and then every column that uses the name gets the same dimension. The trailing `?` on `Embedding1536?` makes the column optional, exactly as `?` did in Prisma ORM 7. The `types` block is part of [PSL syntax](/orm/contract-authoring/psl-syntax). +The `pgvector` in `pgvector.Vector(1536)` is the name the extension registers its types under, and its directory page lists them. PostGIS, for example, gives you `postgis.Geometry(4326)`. + ```prisma title="src/prisma/contract.prisma" types { Embedding1536 = pgvector.Vector(1536) @@ -83,9 +85,9 @@ model Post { Getting the new column from your contract into your database takes these commands, in this order: -1. `npx prisma contract emit`, so that `contract.json` and `contract.d.ts` pick up the new column. Run it again after every later change to `contract.prisma`, because the migration commands read `contract.json` rather than the `.prisma` file. -2. `npx prisma migration plan`, which writes the extension's own migration under `migrations/pgvector/`. That directory is part of your project's migrations, so commit it with the rest of `migrations/`. Until it exists, the next command stops with an error whose `code` is `MIGRATION.CONTRACT_SPACE_LAYOUT_VIOLATION`. -3. `npx prisma db init` on a new database, or `npx prisma db update` on one that already has tables. This runs the extension's migration, which is the `CREATE EXTENSION IF NOT EXISTS vector` statement, and creates the `post` table. +1. `npx prisma contract emit`, so that `contract.json` and `contract.d.ts` pick up the new column. Run it again after every later change to `contract.prisma`. +2. `npx prisma migration plan`, which writes the extension's own migration under `migrations/pgvector/`. That directory is part of your project's migrations, so commit it with the rest of `migrations/`. Skip this and the next command stops with an error whose `code` is `MIGRATION.CONTRACT_SPACE_LAYOUT_VIOLATION`. +3. `npx prisma db init` on a new database, or `npx prisma db update` on one that already has tables, which [Applying a migration](/orm/migrations/applying-a-migration) covers. This runs the extension's migration, which is the `CREATE EXTENSION IF NOT EXISTS vector` statement, and creates the `post` table. You never write that `CREATE EXTENSION` statement yourself, but the PostgreSQL server has to have pgvector available for it to succeed. The local development server the quickstart sets up has it. A stock PostgreSQL image may not, and then the migration fails on that statement. @@ -96,7 +98,9 @@ const embedding: number[] = await embed('about cats'); await db.orm.public.Post.create({ title: 'about cats', embedding }); ``` -Searching for the rows closest to a query vector uses the operations the extension adds, and you write that search with the SQL query builder rather than with `db.orm`. `db.orm.public.Post` is the model, so it uses the name from your contract, and `db.sql.public.post` is the table, so it uses the name as it is in the database. `db.orm` is the ORM API, `db.sql` is the [SQL query builder](/orm/fundamentals/advanced-queries), and `db.runtime()` is what runs a built query. The query vector is a `number[]` of the same length as the stored embeddings: +`public` is the PostgreSQL schema your tables are in. + +Searching for the rows closest to a query vector uses the operations the extension adds, and you write that search with the SQL query builder rather than with `db.orm`. `db.orm` is the ORM API, `db.sql` is the [SQL query builder](/orm/fundamentals/advanced-queries), and `db.runtime()` is what runs a built query. Under `db.orm` you address a model by the name in your contract, so the model `Post` is `db.orm.public.Post`. Under `db.sql` you address a table by the name it has in the database, so the same data is `db.sql.public.post`. In the callback of each builder method, `f` holds the row's columns and `fns` holds the functions you can call on them, including the ones pgvector adds. The query vector is a `number[]` of the same length as the stored embeddings: ```ts title="src/prisma/similarity-search.ts" export async function similaritySearch(queryVector: number[]) { @@ -111,7 +115,7 @@ export async function similaritySearch(queryVector: number[]) { } ``` -In the callback, `f` holds the row's columns and `fns` holds the functions you can call on them, including the ones pgvector adds. `.select('distance', ...)` names the computed column `distance`. `.build()` returns the query as a plain object, and `db.runtime().query(plan)` runs it and returns the rows. +`.select('distance', ...)` names the computed column `distance`, and `.build()` returns the built query as a plain object that `db.runtime().query(...)` runs. Each row that comes back has `id`, `title`, and `distance`, and `distance` is a `number`. Alongside `fns.cosineDistance`, pgvector gives you `fns.cosineSimilarity`. Both take two vectors and return a PostgreSQL `float8`, which you get as a `number`. The difference is which way you sort: order ascending when you sort by distance, because the closest row has the smallest distance, and descending when you sort by similarity. @@ -123,11 +127,11 @@ One installed package supplies both registrations, the one in `prisma.config.ts` ## When a registration is missing [#capabilities] -When your contract uses an extension and `db.ts` does not list it in `extensions`, `postgres(...)` throws an error whose `code` is `RUNTIME.MISSING_EXTENSION_PACK` before any query runs. Add the extension's `runtime` import to `db.ts` to fix it. If the database cannot install the extension, `db init` or `db update` reports it. +When your contract uses an extension and `db.ts` does not list it in `extensions`, `postgres(...)` throws an error whose `code` is `RUNTIME.MISSING_EXTENSION_PACK` before any query runs. The error calls an extension a pack, which is also the name Supabase uses for its command-line entry point. Add the extension's `runtime` import to `db.ts` to fix it. If the database cannot install the extension, `db init` or `db update` reports it. ## Available extensions -Every extension, whether Prisma wrote it or someone in the community did, is listed in the [extension directory](https://www.prisma.io/extensions) with its install command and the registration snippets to copy. The table here is generated from that same registry. +Every extension, whether Prisma wrote it or someone in the community did, is listed in the [extension directory](https://www.prisma.io/extensions) with its install command and the registration snippets to copy. {/* extensions-catalog:start not-tag=middleware */} | Name | What it adds | Package | Databases | By | @@ -145,9 +149,9 @@ Every extension, whether Prisma wrote it or someone in the community did, is lis | [zod-json](https://www.prisma.io/extensions/zod-json) | Typed JSON columns described and enforced by a zod schema. | `prisma-orm-extension-zod-json` | PostgreSQL | [Omar Dulaimi](https://github.com/omar-dulaimi) | {/* extensions-catalog:end */} -The packages that add support for a database are extensions themselves, which is why `@prisma/orm-postgres`, `@prisma/orm-sqlite`, and `@prisma/orm-mongo` are in the table. A project has one database package and any number of other extensions listed next to it in the two `extensions` arrays. +The packages that add support for a database are extensions themselves, which is why `@prisma/orm-postgres`, `@prisma/orm-sqlite`, and `@prisma/orm-mongo` are in the table. -The ones marked experimental work today, but their methods and options are still changing between releases. They are also incomplete in places: ParadeDB's full-text index takes only one option so far, `key_field`, and SQLite is published without a quickstart to follow. Each name in the table links to that extension's directory page, and from there to the package README. Middleware wraps queries rather than adding a database feature, so it is listed on [How middleware works](/orm/middleware/how-middleware-works) instead. +The ones marked experimental work today, but their methods and options are still changing between releases. Each name in the table links to that extension's directory page, and from there to the package README. Middleware wraps queries rather than adding a database feature, so it is listed on [How middleware works](/orm/middleware/how-middleware-works) instead. :::note[Supabase does not follow the recipe above] @@ -189,7 +193,7 @@ export const db = supabase({ If you would rather read a whole working project than a set of snippets, there is a runnable example for each of these extensions: [pgvector](https://github.com/prisma/orm/tree/main/examples/prisma-8-demo), [PostGIS](https://github.com/prisma/orm/tree/main/examples/prisma-8-postgis-demo), [ParadeDB](https://github.com/prisma/orm/tree/main/examples/paradedb-demo), and [Supabase](https://github.com/prisma/orm/tree/main/examples/supabase). -If the extension you need does not exist yet, you can build it. An extension is a versioned npm package with a documented layout, and the [call for extension authors](https://www.prisma.io/blog/prisma-next-call-for-extension-authors) explains how to write and publish one. Once yours is on npm, [submit it to the directory](https://www.prisma.io/extensions/submit), where the form validates your entry and opens the pull request for you. +If the extension you need does not exist yet, you can build it. An extension is an npm package with a documented layout, and the [call for extension authors](https://www.prisma.io/blog/prisma-next-call-for-extension-authors) explains how to write and publish one. Once yours is on npm, [submit it to the directory](https://www.prisma.io/extensions/submit), where the form validates your entry and opens the pull request for you. ## See also @@ -197,4 +201,4 @@ If the extension you need does not exist yet, you can build it. An extension is - [How middleware works](/orm/middleware/how-middleware-works) for wrapping queries rather than adding database features - [Quickstart with PostgreSQL](/prisma-orm/quickstart/postgresql) to set up a project to add extensions to - [Extensions overview](/orm/extensions) for the full catalog, including middleware -- [Prisma ORM overview](/orm) for the contract-first model extensions plug into +- [Prisma ORM overview](/orm) for how your contract, your migrations, and your client fit together From e2fb554ec4a8a09a78449f9556688aa83d39d621 Mon Sep 17 00:00:00 2001 From: willbot Date: Thu, 17 Sep 2026 11:27:50 +0200 Subject: [PATCH 07/10] docs(orm): extensions reader round 4 Fourth and last round, cutting: the entry-point tour is one sentence pointing at the directory page, the database package is named as the one you call directly, the pack aside is gone, the experimental note sits above the table it explains, every snippet imports db, create without a data wrapper links to Coming from Prisma ORM 7, a failed CREATE EXTENSION points at pgvector's install instructions, and supabase() is said to take the same extensions array. Co-Authored-By: Claude Fable 5.1 Signed-off-by: willbot Signed-off-by: Will Madden --- .../content/docs/orm/extensions/index.mdx | 16 ++++---- .../docs/orm/extensions/using-extensions.mdx | 39 ++++++++++++------- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/apps/docs/content/docs/orm/extensions/index.mdx b/apps/docs/content/docs/orm/extensions/index.mdx index a7884f77864..c1db2c49171 100644 --- a/apps/docs/content/docs/orm/extensions/index.mdx +++ b/apps/docs/content/docs/orm/extensions/index.mdx @@ -6,15 +6,15 @@ metaTitle: Prisma ORM extensions metaDescription: 'The catalog of Prisma ORM extensions with a one-line summary of each, and where to go to install one or publish your own.' --- -An extension is an npm package that adds something Prisma ORM does not do on its own: a column type such as a vector or a geometry, an index type, query operations (new functions you can call in a query, such as pgvector's `cosineDistance`), or middleware, which runs your own code before and after every query. Support for a database comes from an extension too, so you install `@prisma/orm-postgres` to use PostgreSQL, or `@prisma/orm-mongo` to use MongoDB. +An extension is an npm package that adds something Prisma ORM does not do on its own: a column type such as a vector or a geometry, an index type, query operations (new functions you can call in a query, such as pgvector's `cosineDistance`), or middleware, which runs your own code before and after every query. To use PostgreSQL you install `@prisma/orm-postgres`, and to use MongoDB you install `@prisma/orm-mongo`, because support for a database comes from an extension too. -Installing the package is not the whole job, because you register an extension in two places: in `prisma.config.ts`, so that the command line tools know about it, and in `db.ts`, where you create the client, so that your queries can use what it adds. The database package is the one whose functions you call, `ormConfig(...)` and `postgres(...)`, and every other extension goes in the `extensions` array of each. [Using extensions](/orm/extensions/using-extensions) shows both registrations, with the install command, for pgvector. Middleware is the exception, because you register it on the client only, and [How middleware works](/orm/middleware/how-middleware-works) covers that. +Installing the package is not the whole job, because you register an extension in two places. It goes in `prisma.config.ts`, so that the command line tools know about it, and in `db.ts`, the file where you create the client, so that your queries can use what it adds. `@prisma/orm-postgres` is the one package you call directly: `ormConfig(...)` in `prisma.config.ts` and `postgres(...)` in `db.ts`. Every other extension is listed in the `extensions` array passed to those two calls. [Using extensions](/orm/extensions/using-extensions) shows both registrations, with the install command, for pgvector. Middleware is the exception, because you register it on the client only, and [How middleware works](/orm/middleware/how-middleware-works) covers that. -Your contract is what Prisma ORM 8 calls your schema, written in [`contract.prisma`](/orm/contract-authoring/the-data-contract). If you have a Prisma ORM 7 project, [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7) says what replaced `schema.prisma`, `@prisma/client`, and `$use`. +Your contract is what Prisma ORM 8 calls your schema: [`contract.prisma`](/orm/contract-authoring/the-data-contract) in place of `schema.prisma`. If you have a Prisma ORM 7 project, [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7) says what replaced `schema.prisma`, `@prisma/client`, and `$use`. ## Catalog -The [extension directory](https://www.prisma.io/extensions) has the install command and the registration snippet for each entry below. There is no MySQL package, and [Supported databases](/orm/supported-databases) says what is planned. +The [extension directory](https://www.prisma.io/extensions) has the install command and the registration snippet for each entry below. There is no MySQL package, and [Supported databases](/orm/supported-databases) says what is planned. An entry marked experimental works today, but its API can change between releases. {/* extensions-catalog:start */} | Name | What it adds | Package | Databases | By | @@ -33,15 +33,13 @@ The [extension directory](https://www.prisma.io/extensions) has the install comm | [zod-json](https://www.prisma.io/extensions/zod-json) | Typed JSON columns described and enforced by a zod schema. | `prisma-orm-extension-zod-json` | PostgreSQL | [Omar Dulaimi](https://github.com/omar-dulaimi) | {/* extensions-catalog:end */} -An entry marked experimental works today, but its API can change between releases. - -The role-bound clients in the Supabase row are client instances pinned to a particular PostgreSQL role, so the database applies that role's row-level security policies to whatever you query through them. +In the Supabase row, role-bound clients are client instances pinned to a particular PostgreSQL role, so the database applies that role's row-level security policies to whatever you query through them. ## Publish your own -An extension you write yourself is an npm package with one entry point for the command line tools and one for your application. For pgvector they are `@prisma/orm-extension-pgvector/control` and `@prisma/orm-extension-pgvector/runtime`. The database package uses `/config` and `/runtime` for the same two jobs, and Supabase publishes its command line side as `/pack`. The extension's directory page tells you which to import. Middleware ships a single entry point instead, because it is only ever registered on the client. +An extension you write yourself is an npm package with one entry point for the command line tools and one for your application. Name your two entry points `control` and `runtime`, as pgvector does and as the [call for extension authors](https://www.prisma.io/blog/prisma-next-call-for-extension-authors) describes. Middleware ships a single entry point instead, because it is only ever registered on the client. -The [call for extension authors](https://www.prisma.io/blog/prisma-next-call-for-extension-authors) describes the layout in full, and to write middleware, follow [Authoring custom middleware](/orm/middleware/authoring-custom-middleware). If you are adding a whole database, `@prisma/orm-postgres` is the one to copy for a SQL database and `@prisma/orm-mongo` for a document database. Once your package is on npm, [submit it to the directory](https://www.prisma.io/extensions/submit), and a maintainer reviews your entry before it appears in the catalog. +To write middleware, follow [Authoring custom middleware](/orm/middleware/authoring-custom-middleware). If you are adding a whole database, `@prisma/orm-postgres` is the one to copy for a SQL database and `@prisma/orm-mongo` for a document database. Once your package is on npm, [submit it to the directory](https://www.prisma.io/extensions/submit), and a maintainer reviews your entry before it appears in the catalog. ## See also diff --git a/apps/docs/content/docs/orm/extensions/using-extensions.mdx b/apps/docs/content/docs/orm/extensions/using-extensions.mdx index 3d86f201821..984b597faba 100644 --- a/apps/docs/content/docs/orm/extensions/using-extensions.mdx +++ b/apps/docs/content/docs/orm/extensions/using-extensions.mdx @@ -10,7 +10,7 @@ An extension is a package that teaches Prisma ORM a database feature it does not Reach for an extension when your application needs one of those database features and you still want everything Prisma ORM gives you for the rest of your data: typed schema declarations, generated TypeScript, migration support, and query helpers. -Your contract is what Prisma ORM 8 calls your schema. In a project made with `npm create prisma@latest` it is `src/prisma/contract.prisma`, in place of `schema.prisma`, and you still describe your models in it. `npx prisma contract emit` writes `contract.json` and `contract.d.ts` from it, and the file where you create the client (`src/prisma/db.ts`) imports those two rather than the `.prisma` file. [The data contract](/orm/contract-authoring/the-data-contract) covers what goes in it. +Your contract is what Prisma ORM 8 calls your schema: `contract.prisma` in place of `schema.prisma`. In a project made with `npm create prisma@latest` it is at `src/prisma/contract.prisma`. Adding an extension means installing its package and then registering it in two places, your config file and your client. These steps use pgvector, the vector search extension, as the example, and they assume a PostgreSQL project from the [quickstart](/prisma-orm/quickstart/postgresql), so `@prisma/orm-postgres` is already installed and the extension package is added next to it. Supabase is set up differently, so if that is the extension you are adding, read [the note under the catalog](#available-extensions) first. @@ -40,9 +40,9 @@ export default definePrismaConfig({ }); ``` -The database package is the one whose functions you call, `ormConfig(...)` and `postgres(...)`, and every other extension goes in the `extensions` array of each. The example renames `defineConfig` to `ormConfig` so it is not confused with `definePrismaConfig`, and you can keep the original name. +`@prisma/orm-postgres` is the one package you call directly: `ormConfig(...)` in `prisma.config.ts` and `postgres(...)` in `db.ts`. Every other extension is listed in the `extensions` array passed to those two calls. The example renames `defineConfig` to `ormConfig` so it is not confused with `definePrismaConfig`, and you can keep the original name. -An extension has one entry point for the command line tools and one for your application. For pgvector they are `@prisma/orm-extension-pgvector/control` and `@prisma/orm-extension-pgvector/runtime`. The database package uses `/config` and `/runtime` for the same two jobs, and Supabase publishes its command-line side as `/pack`. The extension's directory page tells you which to import. +Import the paths the extension's page in the [extension directory](https://www.prisma.io/extensions) gives you. For pgvector they are `/control` in `prisma.config.ts` and `/runtime` in `db.ts`. ## 3. Register it on the client @@ -65,9 +65,9 @@ The `'./contract.d'` in that import is not a typo: it is how you import `contrac ## 4. Use the new type in your contract [#4-use-the-new-type-in-your-schema] -Now that pgvector is registered in the config, its types are available in your contract, so you can give a column a vector type. A vector column needs an explicit number of dimensions, so declare that dimension once as a named type in a `types` block, and then every column that uses the name gets the same dimension. The trailing `?` on `Embedding1536?` makes the column optional, exactly as `?` did in Prisma ORM 7. The `types` block is part of [PSL syntax](/orm/contract-authoring/psl-syntax). +Now that pgvector is in `prisma.config.ts`, `npx prisma contract emit` accepts `pgvector.Vector(1536)` in your contract. A vector column needs an explicit number of dimensions, so declare that dimension once as a named type in a `types` block, and then every column that uses the name gets the same dimension. The trailing `?` on `Embedding1536?` makes the column optional, exactly as `?` did in Prisma ORM 7. The `types` block is part of [PSL syntax](/orm/contract-authoring/psl-syntax). -The `pgvector` in `pgvector.Vector(1536)` is the name the extension registers its types under, and its directory page lists them. PostGIS, for example, gives you `postgis.Geometry(4326)`. +The `pgvector` in `pgvector.Vector(1536)` is the name the extension registers its types under, and its page in the extension directory lists them. PostGIS, for example, gives you `postgis.Geometry(4326)`. ```prisma title="src/prisma/contract.prisma" types { @@ -86,23 +86,34 @@ model Post { Getting the new column from your contract into your database takes these commands, in this order: 1. `npx prisma contract emit`, so that `contract.json` and `contract.d.ts` pick up the new column. Run it again after every later change to `contract.prisma`. -2. `npx prisma migration plan`, which writes the extension's own migration under `migrations/pgvector/`. That directory is part of your project's migrations, so commit it with the rest of `migrations/`. Skip this and the next command stops with an error whose `code` is `MIGRATION.CONTRACT_SPACE_LAYOUT_VIOLATION`. -3. `npx prisma db init` on a new database, or `npx prisma db update` on one that already has tables, which [Applying a migration](/orm/migrations/applying-a-migration) covers. This runs the extension's migration, which is the `CREATE EXTENSION IF NOT EXISTS vector` statement, and creates the `post` table. +2. `npx prisma migration plan`, which writes the extension's own migration into `migrations/`, so commit it along with your other migrations. Skip this and the next command stops with an error whose `code` is `MIGRATION.CONTRACT_SPACE_LAYOUT_VIOLATION`. +3. `npx prisma db init` on a new database, or `npx prisma db update` on one that already has tables, which [Applying a migration](/orm/migrations/applying-a-migration) covers. This runs the extension's migration and creates the `post` table. -You never write that `CREATE EXTENSION` statement yourself, but the PostgreSQL server has to have pgvector available for it to succeed. The local development server the quickstart sets up has it. A stock PostgreSQL image may not, and then the migration fails on that statement. +The extension's migration is the `CREATE EXTENSION IF NOT EXISTS vector` statement, so you never write that statement yourself, but the PostgreSQL server has to have pgvector available for it to succeed. The local development server the quickstart sets up has it. A stock PostgreSQL image may not, and then the migration fails on that statement, so install pgvector on the server first, following [pgvector's own instructions](https://github.com/pgvector/pgvector). With the column in your database, write an embedding into it. A vector value is a plain `number[]` with as many entries as the column's dimension, so a column typed `Embedding1536` takes an array of 1536 numbers, which is what an embedding model gives you. Prisma ORM does not produce the numbers, and `embed` in this example stands for whichever model you call: ```ts title="src/prisma/write-post.ts" +import { db } from './db'; + const embedding: number[] = await embed('about cats'); await db.orm.public.Post.create({ title: 'about cats', embedding }); ``` -`public` is the PostgreSQL schema your tables are in. +In Prisma ORM 8 `create` takes the fields directly, with no `data` wrapper. [Create a record](/orm/coming-from-prisma-orm-7#create-a-record) shows the two side by side. `public` is the PostgreSQL schema your tables are in. + +Searching for the rows closest to a query vector uses the operations the extension adds, and you write that search with the SQL query builder rather than with `db.orm`. In that block: -Searching for the rows closest to a query vector uses the operations the extension adds, and you write that search with the SQL query builder rather than with `db.orm`. `db.orm` is the ORM API, `db.sql` is the [SQL query builder](/orm/fundamentals/advanced-queries), and `db.runtime()` is what runs a built query. Under `db.orm` you address a model by the name in your contract, so the model `Post` is `db.orm.public.Post`. Under `db.sql` you address a table by the name it has in the database, so the same data is `db.sql.public.post`. In the callback of each builder method, `f` holds the row's columns and `fns` holds the functions you can call on them, including the ones pgvector adds. The query vector is a `number[]` of the same length as the stored embeddings: +- `db.orm` is the ORM API, and under it you address a model by the name in your contract, so the model `Post` is `db.orm.public.Post`. +- `db.sql` is the [SQL query builder](/orm/fundamentals/advanced-queries), and under it you address a table by the name it has in the database, so the same data is `db.sql.public.post`. +- `db.runtime()` is what runs a built query. +- `f`, in the callback of each builder method, holds the row's columns. +- `fns` holds the functions you can call on those columns, including the ones pgvector adds. +- `queryVector` is a `number[]` of the same length as the stored embeddings. ```ts title="src/prisma/similarity-search.ts" +import { db } from './db'; + export async function similaritySearch(queryVector: number[]) { const plan = db.sql.public.post .select('id', 'title') @@ -127,7 +138,7 @@ One installed package supplies both registrations, the one in `prisma.config.ts` ## When a registration is missing [#capabilities] -When your contract uses an extension and `db.ts` does not list it in `extensions`, `postgres(...)` throws an error whose `code` is `RUNTIME.MISSING_EXTENSION_PACK` before any query runs. The error calls an extension a pack, which is also the name Supabase uses for its command-line entry point. Add the extension's `runtime` import to `db.ts` to fix it. If the database cannot install the extension, `db init` or `db update` reports it. +When your contract uses an extension and `db.ts` does not list it in `extensions`, `postgres(...)` throws an error whose `code` is `RUNTIME.MISSING_EXTENSION_PACK` before any query runs. Add the extension's `runtime` import to `db.ts` to fix it. If the database cannot install the extension, `db init` or `db update` reports it. ## Available extensions @@ -149,7 +160,7 @@ Every extension, whether Prisma wrote it or someone in the community did, is lis | [zod-json](https://www.prisma.io/extensions/zod-json) | Typed JSON columns described and enforced by a zod schema. | `prisma-orm-extension-zod-json` | PostgreSQL | [Omar Dulaimi](https://github.com/omar-dulaimi) | {/* extensions-catalog:end */} -The packages that add support for a database are extensions themselves, which is why `@prisma/orm-postgres`, `@prisma/orm-sqlite`, and `@prisma/orm-mongo` are in the table. +The database packages are in the table too. The ones marked experimental work today, but their methods and options are still changing between releases. Each name in the table links to that extension's directory page, and from there to the package README. Middleware wraps queries rather than adding a database feature, so it is listed on [How middleware works](/orm/middleware/how-middleware-works) instead. @@ -187,7 +198,9 @@ export const db = supabase({ }); ``` -`db.asUser(jwt)` gives you a client that runs as that user, `db.asAnon()` one that runs as the anonymous role, and `db.asServiceRole()` one that bypasses row-level security, and each of them has the same `orm` and `sql` you use on `db`. The [runnable example](https://github.com/prisma/orm/tree/main/examples/supabase) shows the whole setup in one project. +`supabase(...)` takes the same `extensions` array as `postgres(...)`, so pgvector or another extension goes there in the same way. + +`db.asUser(jwt)` takes the user's Supabase JWT and gives you a client that runs as that user. `db.asAnon()` gives you one that runs as the anonymous role, and `db.asServiceRole()` one that bypasses row-level security. Each of them has the same `orm` and `sql` you use on `db`. The [runnable example](https://github.com/prisma/orm/tree/main/examples/supabase) shows the whole setup in one project. ::: From 684613aa2898edfdb72414eebfa17de880cc69a5 Mon Sep 17 00:00:00 2001 From: willbot Date: Thu, 17 Sep 2026 11:35:50 +0200 Subject: [PATCH 08/10] docs(orm): extensions final fact re-check One Opus checker per page diffed against the round-zero commit. Corrections: Supabase is also called directly, so orm-postgres is no longer the one package you call; supabase() is async and is awaited; the role clients, not db itself, carry orm and sql; distance is null for a row with no embedding. No page gained words. Co-Authored-By: Claude Fable 5.1 Signed-off-by: willbot Signed-off-by: Will Madden --- apps/docs/content/docs/orm/extensions/index.mdx | 2 +- .../docs/content/docs/orm/extensions/using-extensions.mdx | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/docs/content/docs/orm/extensions/index.mdx b/apps/docs/content/docs/orm/extensions/index.mdx index c1db2c49171..37039585013 100644 --- a/apps/docs/content/docs/orm/extensions/index.mdx +++ b/apps/docs/content/docs/orm/extensions/index.mdx @@ -8,7 +8,7 @@ metaDescription: 'The catalog of Prisma ORM extensions with a one-line summary o An extension is an npm package that adds something Prisma ORM does not do on its own: a column type such as a vector or a geometry, an index type, query operations (new functions you can call in a query, such as pgvector's `cosineDistance`), or middleware, which runs your own code before and after every query. To use PostgreSQL you install `@prisma/orm-postgres`, and to use MongoDB you install `@prisma/orm-mongo`, because support for a database comes from an extension too. -Installing the package is not the whole job, because you register an extension in two places. It goes in `prisma.config.ts`, so that the command line tools know about it, and in `db.ts`, the file where you create the client, so that your queries can use what it adds. `@prisma/orm-postgres` is the one package you call directly: `ormConfig(...)` in `prisma.config.ts` and `postgres(...)` in `db.ts`. Every other extension is listed in the `extensions` array passed to those two calls. [Using extensions](/orm/extensions/using-extensions) shows both registrations, with the install command, for pgvector. Middleware is the exception, because you register it on the client only, and [How middleware works](/orm/middleware/how-middleware-works) covers that. +Installing the package is not the whole job, because you register an extension in two places. It goes in `prisma.config.ts`, so that the command line tools know about it, and in `db.ts`, the file where you create the client, so that your queries can use what it adds. `@prisma/orm-postgres` is the package you call directly: `ormConfig(...)` in `prisma.config.ts` and `postgres(...)` in `db.ts`. Other extensions are listed in the `extensions` array passed to those two calls. [Using extensions](/orm/extensions/using-extensions) shows both registrations, with the install command, for pgvector. Middleware is the exception, because you register it on the client only, and [How middleware works](/orm/middleware/how-middleware-works) covers that. Your contract is what Prisma ORM 8 calls your schema: [`contract.prisma`](/orm/contract-authoring/the-data-contract) in place of `schema.prisma`. If you have a Prisma ORM 7 project, [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7) says what replaced `schema.prisma`, `@prisma/client`, and `$use`. diff --git a/apps/docs/content/docs/orm/extensions/using-extensions.mdx b/apps/docs/content/docs/orm/extensions/using-extensions.mdx index 984b597faba..7b367e20447 100644 --- a/apps/docs/content/docs/orm/extensions/using-extensions.mdx +++ b/apps/docs/content/docs/orm/extensions/using-extensions.mdx @@ -40,7 +40,7 @@ export default definePrismaConfig({ }); ``` -`@prisma/orm-postgres` is the one package you call directly: `ormConfig(...)` in `prisma.config.ts` and `postgres(...)` in `db.ts`. Every other extension is listed in the `extensions` array passed to those two calls. The example renames `defineConfig` to `ormConfig` so it is not confused with `definePrismaConfig`, and you can keep the original name. +`@prisma/orm-postgres` is the package you call directly: `ormConfig(...)` in `prisma.config.ts` and `postgres(...)` in `db.ts`. Other extensions are listed in the `extensions` array passed to those two calls. The example renames `defineConfig` to `ormConfig` so it is not confused with `definePrismaConfig`, and you can keep the original name. Import the paths the extension's page in the [extension directory](https://www.prisma.io/extensions) gives you. For pgvector they are `/control` in `prisma.config.ts` and `/runtime` in `db.ts`. @@ -126,7 +126,7 @@ export async function similaritySearch(queryVector: number[]) { } ``` -`.select('distance', ...)` names the computed column `distance`, and `.build()` returns the built query as a plain object that `db.runtime().query(...)` runs. Each row that comes back has `id`, `title`, and `distance`, and `distance` is a `number`. +`.select('distance', ...)` names the computed column `distance`, and `.build()` returns a plain object that `db.runtime().query(...)` runs. Each row that comes back has `id`, `title`, and `distance`, a `number` that is `null` when the row has no embedding. Alongside `fns.cosineDistance`, pgvector gives you `fns.cosineSimilarity`. Both take two vectors and return a PostgreSQL `float8`, which you get as a `number`. The difference is which way you sort: order ascending when you sort by distance, because the closest row has the smallest distance, and descending when you sort by similarity. @@ -191,7 +191,7 @@ import { supabase } from '@prisma/orm-extension-supabase/runtime'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -export const db = supabase({ +export const db = await supabase({ contractJson, url: process.env['DATABASE_URL']!, jwtSecret: process.env['SUPABASE_JWT_SECRET']!, @@ -200,7 +200,7 @@ export const db = supabase({ `supabase(...)` takes the same `extensions` array as `postgres(...)`, so pgvector or another extension goes there in the same way. -`db.asUser(jwt)` takes the user's Supabase JWT and gives you a client that runs as that user. `db.asAnon()` gives you one that runs as the anonymous role, and `db.asServiceRole()` one that bypasses row-level security. Each of them has the same `orm` and `sql` you use on `db`. The [runnable example](https://github.com/prisma/orm/tree/main/examples/supabase) shows the whole setup in one project. +`await db.asUser(jwt)` takes the user's Supabase JWT and gives you a client that runs as that user. `db.asAnon()` gives you one that runs as the anonymous role, and `db.asServiceRole()` one that bypasses row-level security. `db` itself has no `orm` or `sql`: those live on the three role-bound clients. The [runnable example](https://github.com/prisma/orm/tree/main/examples/supabase) shows the whole setup in one project. ::: From a3f654f7d530d33fa8f9facfb301ca4b1d2ce010 Mon Sep 17 00:00:00 2001 From: reviewer Date: Thu, 17 Sep 2026 09:45:14 +0000 Subject: [PATCH 09/10] docs(docs): use jwksUrl in the Supabase client example and name the quickstart databases The Supabase client block passed jwtSecret only. The extension's README at the rc.11 commit says jwksUrl is the option for a current Supabase project, which signs its JWTs with ES256 keys, and jwtSecret is for older HS256 projects; a token signed the other way is rejected with SUPABASE.JWT_INVALID. The block now passes jwksUrl and the text under it says when jwtSecret applies instead. The pgvector paragraph said the quickstart sets up a local development server. The quickstart offers a local Prisma Postgres database through Composer or one from create-db, so the sentence now names those two. --- .../docs/content/docs/orm/extensions/using-extensions.mdx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/docs/content/docs/orm/extensions/using-extensions.mdx b/apps/docs/content/docs/orm/extensions/using-extensions.mdx index 7b367e20447..5adb0d480ae 100644 --- a/apps/docs/content/docs/orm/extensions/using-extensions.mdx +++ b/apps/docs/content/docs/orm/extensions/using-extensions.mdx @@ -89,7 +89,7 @@ Getting the new column from your contract into your database takes these command 2. `npx prisma migration plan`, which writes the extension's own migration into `migrations/`, so commit it along with your other migrations. Skip this and the next command stops with an error whose `code` is `MIGRATION.CONTRACT_SPACE_LAYOUT_VIOLATION`. 3. `npx prisma db init` on a new database, or `npx prisma db update` on one that already has tables, which [Applying a migration](/orm/migrations/applying-a-migration) covers. This runs the extension's migration and creates the `post` table. -The extension's migration is the `CREATE EXTENSION IF NOT EXISTS vector` statement, so you never write that statement yourself, but the PostgreSQL server has to have pgvector available for it to succeed. The local development server the quickstart sets up has it. A stock PostgreSQL image may not, and then the migration fails on that statement, so install pgvector on the server first, following [pgvector's own instructions](https://github.com/pgvector/pgvector). +The extension's migration is the `CREATE EXTENSION IF NOT EXISTS vector` statement, so you never write that statement yourself, but the PostgreSQL server has to have pgvector available for it to succeed. Both databases the quickstart offers have it: the local Prisma Postgres database that Composer starts, and a Prisma Postgres database from `npx create-db@latest`. A stock PostgreSQL image may not, and then the migration fails on that statement, so install pgvector on the server first, following [pgvector's own instructions](https://github.com/pgvector/pgvector). With the column in your database, write an embedding into it. A vector value is a plain `number[]` with as many entries as the column's dimension, so a column typed `Embedding1536` takes an array of 1536 numbers, which is what an embedding model gives you. Prisma ORM does not produce the numbers, and `embed` in this example stands for whichever model you call: @@ -184,7 +184,7 @@ export default definePrismaConfig({ }); ``` -The client is where the real difference is, because you build it with `supabase()` from `@prisma/orm-extension-supabase/runtime` rather than with `postgres()`, and it takes the secret it needs to check JWTs: +The client is where the real difference is, because you build it with `supabase()` from `@prisma/orm-extension-supabase/runtime` rather than with `postgres()`, and it needs a way to check the JWTs your users send: ```ts title="src/prisma/db.ts" import { supabase } from '@prisma/orm-extension-supabase/runtime'; @@ -194,10 +194,12 @@ import contractJson from './contract.json' with { type: 'json' }; export const db = await supabase({ contractJson, url: process.env['DATABASE_URL']!, - jwtSecret: process.env['SUPABASE_JWT_SECRET']!, + jwksUrl: process.env['SUPABASE_JWKS_URL']!, }); ``` +`jwksUrl` is your project's JWKS endpoint, `https://.supabase.co/auth/v1/.well-known/jwks.json`, and it is the right option for a current Supabase project, because those sign their JWTs with ES256 keys. An older project that still signs with the shared HS256 secret passes `jwtSecret` instead of `jwksUrl`, and never both. `supabase status` prints a `JWT_SECRET` even for an ES256 project, so its presence does not tell you which one yours uses. A token signed the other way is rejected with an error whose `code` is `SUPABASE.JWT_INVALID`, and the message names the fix. + `supabase(...)` takes the same `extensions` array as `postgres(...)`, so pgvector or another extension goes there in the same way. `await db.asUser(jwt)` takes the user's Supabase JWT and gives you a client that runs as that user. `db.asAnon()` gives you one that runs as the anonymous role, and `db.asServiceRole()` one that bypasses row-level security. `db` itself has no `orm` or `sql`: those live on the three role-bound clients. The [runnable example](https://github.com/prisma/orm/tree/main/examples/supabase) shows the whole setup in one project. From 64c72603bcc863db82a98cc575c00b42e951e86a Mon Sep 17 00:00:00 2001 From: Ankur Datta <64993082+ankur-arch@users.noreply.github.com> Date: Thu, 17 Sep 2026 14:07:54 +0200 Subject: [PATCH 10/10] fix(site): use jwksUrl in the Supabase directory snippet The extension directory's hand-written Supabase snippet still passed jwtSecret, while the docs page on this branch already moved to jwksUrl. Current Supabase projects sign ES256, so jwtSecret rejects every asUser call with SUPABASE.JWT_INVALID. Match the docs page: await supabase(), pass jwksUrl, and note that jwtSecret is for legacy HS256 projects only. Co-Authored-By: Claude Fable 5.1 --- apps/site/src/components/extensions/usage-snippets.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/site/src/components/extensions/usage-snippets.ts b/apps/site/src/components/extensions/usage-snippets.ts index 13c81edcfa6..45115d6bdb4 100644 --- a/apps/site/src/components/extensions/usage-snippets.ts +++ b/apps/site/src/components/extensions/usage-snippets.ts @@ -175,13 +175,15 @@ export default definePrismaConfig({ import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; -export const db = supabase({ +export const db = await supabase({ contractJson, url: process.env['DATABASE_URL']!, - jwtSecret: process.env['SUPABASE_JWT_SECRET']!, + // Current projects sign ES256: point at your JWKS endpoint. A legacy + // HS256 project passes jwtSecret instead, never both. + jwksUrl: process.env['SUPABASE_JWKS_URL']!, }); -// Per request: db.asUser(jwt), db.asAnon(), or db.asServiceRole().`, +// Per request: await db.asUser(jwt), db.asAnon(), or db.asServiceRole().`, }, ], "middleware-cache": middlewareRegistration(