Skip to main content
Prisma Documentation Docs

Search documentation

Type to search this documentation.

On this pageOverview

Using 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 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 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, 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 first.

bun
bun add @prisma/orm-extension-pgvector
pnpm
pnpm add @prisma/orm-extension-pgvector
yarn
yarn add @prisma/orm-extension-pgvector
npm
npm install @prisma/orm-extension-pgvector

A project made with npm create prisma@latest already has prisma.config.ts, so open it and add the extension to the extensions array.

title="prisma.config.ts"
import { definePrismaConfig } from 'prisma/config';

import pgvector from '@prisma/orm-extension-pgvector/control';

import { defineConfig as ormConfig } from '@prisma/orm-postgres/config';

export default definePrismaConfig({

  orm: ormConfig({

    contract: './src/prisma/contract.prisma',

    extensions: [pgvector],

    db: {

      connection: process.env['DATABASE_URL']!,

    },

  }),

});

@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 gives you. For pgvector they are /control in prisma.config.ts and /runtime in db.ts.

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.

title="src/prisma/db.ts"
import pgvector from '@prisma/orm-extension-pgvector/runtime';

import postgres from '@prisma/orm-postgres/runtime';

import type { Contract } from './contract.d';

import contractJson from './contract.json' with { type: 'json' };

export const db = postgres<Contract>({

  contractJson,

  url: process.env['DATABASE_URL']!,

  extensions: [pgvector],

});

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.

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.

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).

The first line of the file, // use prisma-8, is required. npx prisma contract emit reads only .prisma files whose first line is // use prisma-8 and skips any other file without a warning, so keep that line at the top when you add the types block.

title="src/prisma/contract.prisma"
// use prisma-8

types {

  Embedding1536 = pgvector.Vector(1536)

}

model Post {

  id        String         @id @default(uuid())

  title     String

  embedding Embedding1536?

}

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 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 covers. This runs the extension's migration and makes the database match your contract: it creates the Post table, or adds the embedding column when the Post table already exists.

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.

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:

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 });

In Prisma ORM 8 create takes the fields directly, with no data wrapper. 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:

  • 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, and under it you address a table by the name it has in the database, so the same data is db.sql.public.Post. The two names are the same because the model sets no @@map.
  • 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.
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')

    .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);

}

.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.

One installed package supplies both registrations, the one in prisma.config.ts and the one in db.ts:

One extension package, two registrations, one databaseStep 1 of 4

One package brings everything the extension needs.

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.

Every extension, whether Prisma wrote it or someone in the community did, is listed in the extension directory with its install command and the registration snippets to copy.

Name What it adds Package Databases By
arktype-json JSON columns validated by an arktype schema and typed end to end. @prisma/orm-extension-arktype-json PostgreSQL Prisma
MongoDB MongoDB support for Prisma ORM: config, runtime, contract authoring, BSON values, and migrations in one package. @prisma/orm-mongo MongoDB Prisma
ParadeDB (experimental) BM25 full-text search indexes. @prisma/orm-extension-paradedb PostgreSQL Prisma
pgvector Vector columns and similarity search for embeddings. @prisma/orm-extension-pgvector PostgreSQL Prisma
PostGIS Geometry columns and geospatial queries such as distance and containment. @prisma/orm-extension-postgis PostgreSQL Prisma
PostgreSQL PostgreSQL support for Prisma ORM: config, runtime, contract authoring, and migrations in one package. @prisma/orm-postgres PostgreSQL Prisma
SQLite (experimental) SQLite support for Prisma ORM: config, runtime, contract authoring, and migrations in one package. @prisma/orm-sqlite SQLite Prisma
Supabase (experimental) Supabase auth and storage tables plus role-bound clients for row-level security. @prisma/orm-extension-supabase PostgreSQL Prisma
IndexedDB (experimental) Prisma 8 for IndexedDB: a browser database from your PSL schema, with typed accessors and explicit migrations. @prisma-idb/client-idb IndexedDB Yash Kolekar
typed-json Typed JSON and text columns with no validator dependency. prisma-orm-extension-typed-json PostgreSQL Omar Dulaimi
zod-json Typed JSON columns described and enforced by a zod schema. prisma-orm-extension-zod-json PostgreSQL Omar Dulaimi

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 instead.

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, PostGIS, ParadeDB, and Supabase.

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 explains how to write and publish one. Once yours is on npm, submit it to the directory, where the form validates your entry and opens the pull request for you.

Suggest an edit

Propose a replacement for this page. The site team reviews it before applying any changes.

Export
Documentation menu