# Prisma ORM 7 to 8 (PostgreSQL)

This guide is for teams running a Prisma ORM 7 application on PostgreSQL who want to move to **Prisma ORM 8** without a rewrite. You will install Prisma ORM 8 next to Prisma ORM 7 in the same application, move routes over one at a time, hand migration ownership to Prisma ORM 8, and remove Prisma ORM 7 once nothing depends on it.

Both versions run against the same PostgreSQL database the whole time. The database, its data, and its connection string do not change; only application code and tooling do. Because each route stays on Prisma ORM 7 until you deliberately move it, the application remains shippable at every point in the migration.

The guide covers **PostgreSQL only**. Guidance for other databases will follow. If you are coming from v6 on MongoDB, see the [MongoDB guide](/guides/upgrade-prisma-orm-mongodb).

:::callout{intent="note"}
Step 2.1 installs the `latest` version of both Prisma ORM 8 packages: `prisma`, the Prisma ORM 8 CLI, and `@prisma/orm-postgres`. This guide was written with `prisma` at `8.0.0-rc.17` and `@prisma/orm-postgres` at `8.0.0-rc.13`: the CLI is released separately, so the two numbers differ. Prisma ORM 7 stays at `7.10.0`: the example app in step 1.1 starts on it, and step 1.2 installs `@prisma/prisma7@7.10.0`.
:::

## [How the incremental migration works](#how-the-incremental-migration-works)

The migration runs in five phases. The application works at the end of each one.

1. **Prepare Prisma ORM 7 for side-by-side operation.** Move Prisma ORM 7 onto its own package name, binary, and config file. No behavior changes.
2. **Add Prisma ORM 8.** Install the Prisma ORM 8 CLI and runtime with their own config, schema contract, and generated client. No application code uses them yet.
3. **Migrate one route.** One route runs on Prisma ORM 8 while the rest stay on Prisma ORM 7, all against the same database.
4. **Transfer migration ownership.** Prisma ORM 8 takes over planning and applying schema changes.
5. **Remove Prisma ORM 7** once nothing imports it.

The ownership timeline matters more than the code timeline. Prisma ORM 7 owns schema migrations through phases 1 to 3, and routes move to Prisma ORM 8 independently of that. Prisma ORM 8 takes over migrations only in phase 4, after a database signature and a `db` ref are in place; the first `migration plan` then writes the baseline migration itself. You can pause between phases for as long as you need.

The [prisma8-and-7-example](https://github.com/prisma/prisma8-and-7-example) repository shows the finished result of each phase (tags `step-0` through `step-3`).

## [Prerequisites](#prerequisites)

- **Node.js 22.18 or newer (on the 24 line, 24.11 or newer)**; Node.js 24 recommended
- A working **Prisma ORM 7** application on PostgreSQL: `prisma.config.ts`, the `prisma-client` generator, and a driver adapter
- **TypeScript 5.9 or newer** with `"strict": true`. [Step 2.6](#26-set-up-tsconfigjson-for-the-prisma-orm-8-client) tells you whether to change the `module` setting in `tsconfig.json`

## [1. Prepare Prisma ORM 7 for side-by-side operation](#1-prepare-prisma-orm-7-for-side-by-side-operation)

Prisma ORM 8 expects the `prisma` package name, the `prisma` binary, and the `prisma.config.ts` file name. In this phase you move Prisma ORM 7 off those three names so Prisma ORM 8 can take them without ambiguity. Nothing migrates yet.

### [1.1. Confirm the application works](#11-confirm-the-application-works)

The guide follows a small Hono API with two routes. Map the file names to your own project. The Prisma ORM 7 pieces that matter:

```title="package.json (excerpt)"
{

  "scripts": {

    "prisma:generate": "prisma generate",

    "db:migrate": "prisma migrate dev"

  },

  "dependencies": {

    "@prisma/adapter-pg": "^7.10.0",

    "@prisma/client": "^7.10.0"

  },

  "devDependencies": {

    "prisma": "^7.10.0"

  }

}
```

```title="prisma/schema.prisma"
generator client {

  provider = "prisma-client"

  output   = "../generated/prisma"

}

datasource db {

  provider = "postgresql"

}

model User {

  id    Int     @id @default(autoincrement())

  email String  @unique

  name  String?

  posts Post[]

}

model Post {

  id        Int     @id @default(autoincrement())

  title     String

  published Boolean @default(false)

  authorId  Int

  author    User    @relation(fields: [authorId], references: [id], onDelete: Cascade)

  @@index([authorId])

}
```

```title="prisma.config.ts"
import "dotenv/config";

import { defineConfig } from "prisma/config";

export default defineConfig({

  schema: "prisma/schema.prisma",

  migrations: {

    path: "prisma/migrations",

  },

  datasource: {

    url: process.env["DATABASE_URL"],

  },

});
```

```title="src/db.ts"
import "dotenv/config";

import { PrismaPg } from "@prisma/adapter-pg";

import { PrismaClient } from "../generated/prisma/client.js";

const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL! });

export const prisma = new PrismaClient({ adapter });
```

Two routes read and write through this client, `/users` and `/posts`:

```title="src/routes/users.ts"
import { Hono } from "hono";

import { prisma } from "../db.js";

export const users = new Hono();

users.get("/", async (c) => {

  const result = await prisma.user.findMany({

    include: { posts: true },

    orderBy: { id: "asc" },

  });

  return c.json(result);

});

users.post("/", async (c) => {

  const body = await c.req.json<{ email: string; name?: string }>();

  const user = await prisma.user.create({ data: body });

  return c.json(user, 201);

});
```

`src/routes/posts.ts` follows the same pattern for `Post`.

Start the app and run a read and a write:

::::tabs
:::tab{title="bun"}
```
bun run dev
```
:::

:::tab{title="pnpm"}
```bash
pnpm run dev
```
:::

:::tab{title="yarn"}
```bash
yarn dev
```
:::

:::tab{title="npm"}
```bash
npm run dev
```

```bash
curl -X POST localhost:3000/users -H 'content-type: application/json' \
  -d '{"email":"alice@prisma.io","name":"Alice"}'
curl localhost:3000/users
```

Do not continue until both requests succeed. That confirms the Prisma ORM 7 application works before you change its configuration.
:::
::::

```
curl -X POST localhost:3000/users -H 'content-type: application/json' \

  -d '{"email":"alice@prisma.io","name":"Alice"}'

curl localhost:3000/users
```

Do not continue until both requests succeed. That confirms the Prisma ORM 7 application works before you change its configuration.

### [1.2. Replace the `prisma` package with `@prisma/prisma7`](#12-replace-the-prisma-package-with-prismaprisma7)

::::tabs
:::tab{title="bun"}
```
bun remove prisma

bun add --dev @prisma/prisma7@7.10.0
```
:::

:::tab{title="pnpm"}
```bash
pnpm remove prisma
pnpm add --save-dev @prisma/prisma7@7.10.0
```
:::

:::tab{title="yarn"}
```bash
yarn remove prisma
yarn add --dev @prisma/prisma7@7.10.0
```
:::

:::tab{title="npm"}
```bash
npm uninstall prisma
npm install --save-dev @prisma/prisma7@7.10.0
```

`@prisma/prisma7` is the same Prisma ORM 7 CLI under a version-specific name. It exposes a `prisma7` binary and keeps `prisma` 7 as a transitive dependency. Your `@prisma/client` and `@prisma/adapter-pg` dependencies stay untouched.
:::
::::

`@prisma/prisma7` is the same Prisma ORM 7 CLI under a version-specific name. It exposes a `prisma7` binary and keeps `prisma` 7 as a transitive dependency. Your `@prisma/client` and `@prisma/adapter-pg` dependencies stay untouched.

### [1.3. Rename the Prisma ORM 7 config](#13-rename-the-prisma-orm-7-config)

```
mv prisma.config.ts prisma7.config.ts
```

```title="prisma7.config.ts"
import "dotenv/config";

import { defineConfig } from "prisma/config"; 

import { defineConfig } from "@prisma/prisma7/config"; 

export default defineConfig({

  schema: "prisma/schema.prisma",

  migrations: {

    path: "prisma/migrations",

  },

  datasource: {

    url: process.env["DATABASE_URL"],

  },

});
```

The `prisma7` CLI discovers `prisma7.config.ts` automatically, so no `--config` flag is needed. Renaming frees the `prisma.config.ts` name for Prisma ORM 8, which only accepts its own config format under that name.

### [1.4. Point scripts at the `prisma7` binary](#14-point-scripts-at-the-prisma7-binary)

```title="package.json (excerpt)"
{

  "scripts": {

    "prisma:generate": "prisma generate", 

    "db:migrate": "prisma migrate dev"

    "prisma7:generate": "prisma7 generate", 

    "prisma7:migrate": "prisma7 migrate dev"

  }

}
```

After Prisma ORM 8 is installed, the `prisma` binary runs the Prisma ORM 8 CLI. Update every script, CI job, and deployment command that must continue using Prisma ORM 7 to call `prisma7` instead.

### [1.5. Check that Prisma ORM 7 still works](#15-check-that-prisma-orm-7-still-works)

::::tabs
:::tab{title="bun"}
```
bunx prisma7 generate

bunx prisma7 migrate status
```
:::

:::tab{title="pnpm"}
```bash
pnpm dlx prisma7 generate
pnpm dlx prisma7 migrate status
```
:::

:::tab{title="yarn"}
```bash
yarn dlx prisma7 generate
yarn dlx prisma7 migrate status
```
:::

:::tab{title="npm"}
```bash
npx prisma7 generate
npx prisma7 migrate status
```

**Expected result:** `generate` writes the client to `generated/prisma` as before, and `migrate status` reports that the database schema is up to date. Start the app and query each route; behavior should be identical to step 1.1.
:::
::::

**Expected result:** `generate` writes the client to `generated/prisma` as before, and `migrate status` reports that the database schema is up to date. Start the app and query each route; behavior should be identical to step 1.1.

## [2. Add Prisma ORM 8](#2-add-prisma-orm-8)

### [2.1. Install the Prisma ORM 8 packages](#21-install-the-prisma-orm-8-packages)

::::tabs
:::tab{title="bun"}
```
bun add --dev prisma@latest

bun add @prisma/orm-postgres
```
:::

:::tab{title="pnpm"}
```bash
pnpm add --save-dev prisma@latest
pnpm add @prisma/orm-postgres
```
:::

:::tab{title="yarn"}
```bash
yarn add --dev prisma@latest
yarn add @prisma/orm-postgres
```
:::

:::tab{title="npm"}
```bash
npm install --save-dev prisma@latest
npm install @prisma/orm-postgres
```

`prisma@latest` is the Prisma ORM 8 CLI, and its `prisma/config` subpath provides `definePrismaConfig` for the Prisma ORM 8 config file. Installing it locally (not only running it through `npx`) is what makes that import resolve. `@prisma/orm-postgres` is the PostgreSQL ORM runtime your application code will import.

After this install, `npx prisma <command>` runs the Prisma ORM 8 CLI and `npx prisma7 <command>` runs Prisma ORM 7:
:::
::::

`prisma@latest` is the Prisma ORM 8 CLI, and its `prisma/config` subpath provides `definePrismaConfig` for the Prisma ORM 8 config file. Installing it locally (not only running it through `npx`) is what makes that import resolve. `@prisma/orm-postgres` is the PostgreSQL ORM runtime your application code will import.

After this install, `npx prisma <command>` runs the Prisma ORM 8 CLI and `npx prisma7 <command>` runs Prisma ORM 7:

::::tabs
:::tab{title="bun"}
```
bunx prisma --version
```
:::

:::tab{title="pnpm"}
```bash
pnpm prisma --version
```
:::

:::tab{title="yarn"}
```bash
yarn prisma --version
```
:::

:::tab{title="npm"}
```bash
npx prisma --version
```

**Expected result:** the command prints `8.0.0-rc.17` or a newer version. That number does not match the version of `@prisma/orm-postgres`, because the CLI is released separately.
:::
::::

**Expected result:** the command prints `8.0.0-rc.17` or a newer version. That number does not match the version of `@prisma/orm-postgres`, because the CLI is released separately.

### [2.2. Create the Prisma ORM 8 config](#22-create-the-prisma-orm-8-config)

```title="prisma.config.ts"
import "dotenv/config";

import { definePrismaConfig } from "prisma/config";

import { defineConfig as definePostgresConfig } from "@prisma/orm-postgres/config";

export default definePrismaConfig({

  orm: definePostgresConfig({

    contract: "prisma8/contract.prisma",

    output: "generated/prisma8",

    db: {

      connection: process.env["DATABASE_URL"],

    },

  }),

});
```

Both configs point at the **same** `DATABASE_URL`. Everything else is separate:

|                  | Prisma ORM 7           | Prisma ORM 8              |
| ---------------- | ---------------------- | ------------------------- |
| CLI              | `prisma7`              | `prisma`                  |
| Config           | `prisma7.config.ts`    | `prisma.config.ts`        |
| Schema           | `prisma/schema.prisma` | `prisma8/contract.prisma` |
| Generated client | `generated/prisma`     | `generated/prisma8`       |

Because the config carries the connection, the Prisma ORM 8 CLI commands below don't need a `--db` flag.

::::callout{intent="note"}
Read the Prisma ORM 7 schema instead

Instead of generating a contract file in steps 2.3 and 2.4, Prisma ORM 8 can read `prisma/schema.prisma` directly as its contract, so you keep one schema file while Prisma ORM 7 owns migrations. `schema.prisma` does not need the `// use prisma-8` first line that a contract file written for Prisma ORM 8 starts with (step 2.4 shows one). Skip steps 2.3 and 2.4, and change two lines in the `prisma.config.ts` you just wrote:

```title="prisma.config.ts"
import "dotenv/config";

import { definePrismaConfig } from "prisma/config";

import { defineConfig as definePostgresConfig } from "@prisma/orm-postgres/config"; 

import { defineConfig as definePostgresConfig, prisma7Schema } from "@prisma/orm-postgres/config"; 

export default definePrismaConfig({

  orm: definePostgresConfig({

    contract: "prisma8/contract.prisma", 

    contract: prisma7Schema("prisma/schema.prisma"), 

    output: "generated/prisma8",

    db: {

      connection: process.env["DATABASE_URL"],

    },

  }),

});
```

After step 2.5, run `npx prisma db sign`. [`db sign`](/guides/orm-db-sign) checks that the database matches the contract Prisma ORM 8 read from `schema.prisma` and records in the database that it does. Signing does not move your migrations to Prisma ORM 8. Prisma ORM 7 keeps planning and applying them, so after each Prisma ORM 7 migration, whether you ran `prisma7 migrate dev` or `prisma7 migrate deploy`, run `npx prisma contract emit` and then `npx prisma db sign` again. If you emit without signing, the Prisma ORM 8 client still runs queries, but `npx prisma db verify` reports that the contract no longer matches the one recorded in the database. [Use a Prisma ORM 7 schema](/guides/introduction-6-configuration#use-a-prisma-orm-7-schema) lists the parts of a Prisma ORM 7 schema that Prisma ORM 8 cannot read.

If you have not started phase 1 yet, one command does phase 1 and this setup for you:

:::code-group
```bash title="bun"
bunx prisma@latest orm init --from-prisma7-schema prisma/schema.prisma
```

```bash title="pnpm"
pnpm prisma contract infer --output prisma8/contract.prisma
```

```bash title="yarn"
yarn prisma contract infer --output prisma8/contract.prisma
```

```bash title="npm"
npx prisma contract infer --output prisma8/contract.prisma
```
:::

Write `prisma@latest`, because in a Prisma ORM 7 project `npx prisma` runs version 7. The command moves Prisma ORM 7 to the `prisma7` command and `prisma7.config.ts`, installs the Prisma ORM 8 packages, writes `prisma.config.ts` and a Prisma ORM 8 client in `src/prisma/db.ts`, and emits the contract. [`orm init` on a Prisma ORM 7 project](/guides/orm-orm-init#on-a-prisma-orm-7-project) lists what it changes. Then run `npx prisma db sign` and continue with phase 3, using the `db` client from `src/prisma/db.ts` instead of adding one to `src/db.ts` in step 3.1.

Prisma ORM 7 keeps owning migrations on this path until you are ready for [phase 4](#4-transfer-migration-ownership), where Prisma ORM 8 takes them over. Phase 4 needs a contract file written for Prisma ORM 8, so switch to one first. Either run `npx prisma contract print --output prisma8/contract.prisma`, which writes the contract Prisma ORM 8 read from `schema.prisma` as a Prisma ORM 8 contract file, or run steps 2.3 to 2.5. Then set `contract` in `prisma.config.ts` to `"prisma8/contract.prisma"`, the file you created. Then follow phase 4 as written. Its first step, `db sign`, checks the new contract against the database before Prisma ORM 8 takes over.
::::

### [2.3. Infer the contract from the live database](#23-infer-the-contract-from-the-live-database)

Prisma ORM 8 describes your schema as a [contract](/guides/contract-authoring-the-data-contract). Generate it from the database Prisma ORM 7 built:

::::tabs
:::tab{title="bun"}
```bash
bunx prisma contract infer --output prisma8/contract.prisma
```
:::

:::tab{title="pnpm"}
```bash
pnpm prisma contract emit
```
:::

:::tab{title="yarn"}
```bash
yarn prisma contract emit
```
:::

:::tab{title="npm"}
```bash
npx prisma contract emit
```

`contract emit` writes `contract.json` and `contract.d.ts` to `generated/prisma8`, the runtime and type inputs for the Prisma ORM 8 client. Re-run it after every contract change.
:::
::::

### [2.4. Edit the inferred contract](#24-edit-the-inferred-contract)

`contract infer` reads every table in the database, so it also writes a `PrismaMigrations` model for `_prisma_migrations`, the table where Prisma ORM 7 records which migrations it has applied. Delete that model from the contract, because Prisma ORM 8 must not manage the table. The table itself stays in the database, and an extra table that the contract does not describe is fine.

You do not need to add `@@map` to the other models: a model without `@@map` uses its name, exactly as written, as its table name, so `User` reads and writes the `"User"` table that Prisma ORM 7 created.

After that edit, the contract looks like this:

```title="prisma8/contract.prisma"
// use prisma-8

model User {

  id    Int     @id(map: "User_pkey") @default(autoincrement())

  email String

  name  String?

  posts Post[]

  @@index([email], map: "User_email_key", unique: true)

}

model Post {

  id        Int     @id(map: "Post_pkey") @default(autoincrement())

  title     String

  published Boolean @default(false)

  authorId  Int

  author    User    @relation(fields: [authorId], references: [id], onDelete: Cascade, onUpdate: Cascade, map: "Post_authorId_fkey")

  @@index([authorId], map: "Post_authorId_idx")

}
```

Keep `// use prisma-8` as the first line of `prisma8/contract.prisma`. `contract emit` reads only `.prisma` files that start with this line, and this is the only file in your contract, so without it `contract emit` fails with `CONTRACT.SOURCE_LOAD_FAILED`. A `schema.prisma` that Prisma ORM 8 reads through `prisma7Schema(...)`, as in the tip at the end of step 2.2, does not need the line.

### [2.5. Emit the contract artifacts](#25-emit-the-contract-artifacts)

::::tabs
:::tab{title="bun"}
```
bunx prisma contract emit
```
:::

:::tab{title="pnpm"}
```bash
pnpm prisma db sign
pnpm prisma migration status
```
:::

:::tab{title="yarn"}
```bash
yarn prisma db sign
yarn prisma migration status
```
:::

:::tab{title="npm"}
```bash
npx prisma db sign
npx prisma migration status
```

`db sign` verifies that the live schema matches the emitted contract, writes Prisma ORM 8's marker at that contract version, stores the contract snapshot under `migrations/snapshots/`, and points a [ref](/guides/migrations-the-migration-graph#name-important-states-with-refs) named `db` at it. `migration plan` starts from that ref, so plans chain from the schema Prisma ORM 7 built and contain only your own changes.

**Expected result:** `Database signed`, and `migration status` shows the current and target contract hashes match with nothing pending.
:::
::::

`contract emit` writes `contract.json` and `contract.d.ts` to `generated/prisma8`, the runtime and type inputs for the Prisma ORM 8 client. Re-run it after every contract change.

### [2.6. Set up tsconfig.json for the Prisma ORM 8 client](#26-set-up-tsconfigjson-for-the-prisma-orm-8-client)

Your `tsconfig.json` must allow the JSON import you will write in step 3.1: `import contractJson from "../generated/prisma8/contract.json" with { type: "json" }`. TypeScript accepts the `with { type: "json" }` part only when `module` is `esnext`, `node18`, `node20`, `nodenext`, or `preserve`, and only in a file it treats as an ES module. Any other value, such as `commonjs`, `es2022`, or `node16`, rejects it, and leaving `module` unset fails too. Take the first case below that matches how your app runs:

- **`module` is already one of the five values and your build works with it, and either `package.json` has `"type": "module"` or `module` is `esnext` or `preserve`.** Keep it. Set `moduleResolution` to `bundler` if `module` is `esnext` or `preserve`, to `nodenext` if `module` is `nodenext`, and remove it if `module` is `node18` or `node20`. A `moduleResolution` of `node` or `node10` cannot resolve the `@prisma/orm-*` packages, so do not leave that in place.

- **You compile with `tsc`, run the output with `node`, and `package.json` has no `"type": "module"` (or has `"type": "commonjs"`).** Your app runs as CommonJS, and it can stay that way. Set `"module": "nodenext"` and `"moduleResolution": "nodenext"`, and do not add `"type": "module"`. Prisma ORM 8 works from CommonJS code, and your relative imports stay as they are, without extensions. TypeScript treats `db.ts` as CommonJS here, so in step 3.1 write the JSON import without the `with` part and copy the other import lines as shown.

  ```title="src/db.ts (CommonJS excerpt)"
  import contractJson from "../generated/prisma8/contract.json";
  ```

- **The app runs through `tsx`, or a bundler such as `vite`, `next`, or `esbuild` builds it, and you never run `tsc` output with plain `node`.** Set `"module": "preserve"` and `"moduleResolution": "bundler"`. You do not need file extensions on relative imports, and the import lines in step 3.1 work as shown. If you run the `.ts` files directly with `node` instead, its type stripping needs the `.ts` extension on every relative import, so add `"allowImportingTsExtensions": true` as well.

- **You compile with `tsc`, run the output with `node`, and `package.json` has `"type": "module"`, even if you use `tsx` in development.** Set `"module": "nodenext"` and `"moduleResolution": "nodenext"`. Node requires a file extension on every relative import when it runs ES modules, so every import that starts with `./` or `../` and points at a `.ts` or `.d.ts` file must end in `.js`, the extension of the compiled output: `import { postsRoutes } from "./routes/posts.js"`, and a directory import such as `./routes` becomes `./routes/index.js`. The `contract.json` import in step 3.1 keeps its `.json` extension and its `with { type: "json" }` part. Until every relative import has its extension, `npx tsc --noEmit` fails and names each one that still needs it. The [example project](https://github.com/prisma/prisma8-and-7-example) is set up this way; [the TypeScript module reference](https://www.typescriptlang.org/docs/handbook/modules/reference.html#node16-node18-nodenext) has the details.

Whichever case applies, add `"resolveJsonModule": true` so TypeScript can type the JSON import, and add `generated/prisma8/**/*.d.ts` to `include` so it sees the emitted `contract.d.ts`. If your `tsconfig.json` has no `include`, TypeScript already picks up every file under the project, so skip that part:

```title="tsconfig.json (excerpt)"
{

  "compilerOptions": {

    "resolveJsonModule": true

  },

  "include": [

    "src/**/*.ts",

    "generated/prisma/**/*.ts",

    "generated/prisma8/**/*.d.ts"

  ]

}
```

**Check:** `npx tsc --noEmit` passes. If you followed the last case, it fails until every relative import of a `.ts` or `.d.ts` file has its `.js` extension. Prisma ORM 8 is now installed and configured, but no application code uses it yet. This check does not test your `module` choice, because nothing imports `contract.json` until step 3.1. If TypeScript reports error TS2823 there, the `module` value rejects the `with` part; come back to this step and pick again. If it reports TS2856, TypeScript compiles `db.ts` as CommonJS, so remove the `with` part as the CommonJS case shows.

## [3. Migrate one route](#3-migrate-one-route)

Pick one small route and move only that code. The rest of the application stays on Prisma ORM 7.

### [3.1. Instantiate both clients](#31-instantiate-both-clients)

```title="src/db.ts"
import "dotenv/config";

import { PrismaPg } from "@prisma/adapter-pg";

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

import type { Contract } from "../generated/prisma8/contract.js"; 

import contractJson from "../generated/prisma8/contract.json" with { type: "json" }; 

import { PrismaClient } from "../generated/prisma/client.js";

const connectionString = process.env.DATABASE_URL!;

const adapter = new PrismaPg({ connectionString });

export const prisma = new PrismaClient({ adapter });

export const db = postgres<Contract>({ url: connectionString, contractJson }); 
```

`prisma` is the Prisma ORM 7 client and `db` is the Prisma ORM 8 client, both connected to the same database.

### [3.2. Rewrite the route](#32-rewrite-the-route)

Move the users route to the Prisma ORM 8 [ORM client](/guides/reference-2-reference-orm-client). Queries start from `db.orm.<schema>.<Model>` (`public` here) and chain instead of taking one options object:

:::code-group
```title="After"
import { Hono } from "hono";

import { db } from "../db.js";

export const users = new Hono();

users.get("/", async (c) => {

  const result = await db.orm.public.User.include("posts", (posts) =>

    posts.orderBy((post) => post.id.asc()),

  )

    .orderBy((user) => user.id.asc())

    .all();

  return c.json(result);

});

users.post("/", async (c) => {

  const body = await c.req.json<{ email: string; name?: string }>();

  const user = await db.orm.public.User.create(body);

  return c.json(user, 201);

});
```

```typescript title="Before"
import { Hono } from "hono";
import { prisma } from "../db.js";

export const users = new Hono();

users.get("/", async (c) => {
  const result = await prisma.user.findMany({
    include: { posts: true },
    orderBy: { id: "asc" },
  });
  return c.json(result);
});

users.post("/", async (c) => {
  const body = await c.req.json<{ email: string; name?: string }>();
  const user = await prisma.user.create({ data: body });
  return c.json(user, 201);
});
```
:::

`src/routes/posts.ts` stays unchanged, on Prisma ORM 7.

### [3.3. Exercise both code paths](#33-exercise-both-code-paths)

Start the app and hit both routes:

```
curl localhost:3000/users

curl -X POST localhost:3000/posts -H 'content-type: application/json' \

  -d '{"title":"Written by Prisma 7","authorId":1}'

curl localhost:3000/users
```

**Expected result:** the first request runs through Prisma ORM 8. The second writes through Prisma ORM 7. The third, through Prisma ORM 8 again, includes the post Prisma ORM 7 just wrote.

Remaining routes can move over the same way, one at a time, on any schedule. Prisma ORM 7 still owns schema migrations in this phase. If the schema changes, run `prisma7 migrate dev`, then re-run `contract infer` and `contract emit` so the Prisma ORM 8 contract stays current.

## [4. Transfer migration ownership](#4-transfer-migration-ownership)

So far every schema change has gone through `prisma7 migrate dev`. In this phase Prisma ORM 8 takes over planning and applying schema changes, and `prisma/schema.prisma` is frozen.

Treat the switch as a decision, not a routine step. After it, your team and your pipelines must stop using the Prisma ORM 7 migration workflow, even though routes still on the Prisma ORM 7 client keep working. See [how migrations work](/guides/migrations-how-migrations-work) for the full picture.

Prisma ORM 8 tracks schema state with four pieces, and the handoff creates each one exactly once:

- A **contract hash** identifies one version of the emitted contract.
- A **migration** is an on-disk package recording how to get from one contract hash to another. `migrate` only replays recorded migrations; it never invents one.
- The **marker** is Prisma ORM 8's record, stored in the database, of which contract hash the database currently satisfies.
- A **ref** is a named pointer at a contract hash. `migration plan` uses the `db` ref as its starting point.

Step 4.1 creates the marker and the ref with one command. The baseline migration is written by the first `migration plan` in step 4.3.

### [4.1. Sign the existing database](#41-sign-the-existing-database)

Your database already has every table the contract describes, so adopt it rather than replay anything:

::::tabs
:::tab{title="bun"}
```
bunx prisma db sign

bunx prisma migration status
```
:::

:::tab{title="pnpm"}
```bash
pnpm prisma contract emit
pnpm prisma migration plan --name add_user_bio
pnpm prisma db migrate --advance-ref db
pnpm prisma db verify
```
:::

:::tab{title="yarn"}
```bash
yarn prisma contract emit
yarn prisma migration plan --name add_user_bio
yarn prisma db migrate --advance-ref db
yarn prisma db verify
```
:::

:::tab{title="npm"}
```bash
npx prisma contract emit
npx prisma migration plan --name add_user_bio
npx prisma db migrate --advance-ref db
npx prisma db verify
```

**Expected result:** `migration plan` reports `Planned baseline + 1 operation(s)` and writes two packages under `migrations/app/`: a baseline that records the schema you adopted in step 4.1, and `add_user_bio` with the single operation `Add column "bio" to "User"`. `db migrate` skips the baseline, because the marker already records that state, and applies `add_user_bio`. `--advance-ref db` moves the ref so the next plan chains correctly, and `db verify` reports that marker and schema match the contract.

Restart the app: the Prisma ORM 8 route returns users with `bio`, and the Prisma ORM 7 route keeps working untouched, because its client doesn't know about the new column. Additive changes like nullable columns are safe next to legacy Prisma ORM 7 code. Be careful with renames or drops of columns that Prisma ORM 7 routes still read.
:::
::::

`db sign` verifies that the live schema matches the emitted contract, writes Prisma ORM 8's marker at that contract version, stores the contract snapshot under `migrations/snapshots/`, and points a [ref](/guides/migrations-the-migration-graph#name-important-states-with-refs) named `db` at it. `migration plan` starts from that ref, so plans chain from the schema Prisma ORM 7 built and contain only your own changes.

**Expected result:** `Database signed`, and `migration status` shows the current and target contract hashes match with nothing pending.

### [4.2. Retire the Prisma ORM 7 migration scripts](#42-retire-the-prisma-orm-7-migration-scripts)

Remove `prisma7 migrate` from your scripts so nobody runs it by accident. Keep `prisma7 generate`, because the legacy routes still need their client:

```title="package.json (excerpt)"
{

  "scripts": {

    "prisma7:generate": "prisma7 generate",

    "prisma7:migrate": "prisma7 migrate dev", 

    "prisma8:migrate": "prisma db migrate --advance-ref db"

  }

}
```

### [4.3. Verify the handoff with a schema change](#43-verify-the-handoff-with-a-schema-change)

Verify the migration handoff with a small additive schema change. Add a field to the contract:

```title="prisma8/contract.prisma (excerpt)"
model User {

  id    Int     @id(map: "User_pkey") @default(autoincrement())

  email String

  name  String?

  bio   String?

  ...

}
```

Emit, plan, and apply:

::::tabs
:::tab{title="bun"}
```
bunx prisma contract emit

bunx prisma migration plan --name add_user_bio

bunx prisma db migrate --advance-ref db

bunx prisma db verify
```
:::

:::tab{title="pnpm"}
```bash
pnpm remove @prisma/prisma7 @prisma/client @prisma/adapter-pg
```
:::

:::tab{title="yarn"}
```bash
yarn remove @prisma/prisma7 @prisma/client @prisma/adapter-pg
```
:::

:::tab{title="npm"}
```bash
npm uninstall @prisma/prisma7 @prisma/client @prisma/adapter-pg
```

```bash
rm prisma7.config.ts
rm -r prisma generated/prisma
```

Then delete the `prisma7:*` scripts from `package.json` and drop `generated/prisma/**/*.ts` from the `include` array in `tsconfig.json`.

Verify the end state:
:::
::::

**Expected result:** `migration plan` reports `Planned baseline + 1 operation(s)` and writes two packages under `migrations/app/`: a baseline that records the schema you adopted in step 4.1, and `add_user_bio` with the single operation `Add column "bio" to "User"`. `db migrate` skips the baseline, because the marker already records that state, and applies `add_user_bio`. `--advance-ref db` moves the ref so the next plan chains correctly, and `db verify` reports that marker and schema match the contract.

Restart the app: the Prisma ORM 8 route returns users with `bio`, and the Prisma ORM 7 route keeps working untouched, because its client doesn't know about the new column. Additive changes like nullable columns are safe next to legacy Prisma ORM 7 code. Be careful with renames or drops of columns that Prisma ORM 7 routes still read.

## [5. Remove Prisma ORM 7](#5-remove-prisma-orm-7)

Migrate the remaining routes as in phase 3. For `posts` here, that means swapping `prisma.post.findMany(...)` for `db.orm.public.Post.include("author").all()` and `prisma.post.create({ data })` for `db.orm.public.Post.create(data)`.

When nothing imports `generated/prisma` anymore, remove Prisma ORM 7:

::::tabs
:::tab{title="bun"}
```
bun remove @prisma/prisma7 @prisma/client @prisma/adapter-pg
```
:::

:::tab{title="pnpm"}
```bash
pnpm tsc --noEmit
pnpm prisma db verify
```
:::

:::tab{title="yarn"}
```bash
yarn tsc --noEmit
yarn prisma db verify
```
:::

:::tab{title="npm"}
```bash
npx tsc --noEmit
npx prisma db verify
```

Start the app and run a query against every route. The application now runs entirely on Prisma ORM 8, with schema changes managed by `prisma migration plan` and `prisma db migrate`.

> \[!NOTE]
> Prisma ORM 7's `_prisma_migrations` table remains in the database. It is inert (Prisma ORM 8 ignores it) and you can drop it whenever you like.
:::
::::

```
rm prisma7.config.ts

rm -r prisma generated/prisma
```

Then delete the `prisma7:*` scripts from `package.json` and drop `generated/prisma/**/*.ts` from the `include` array in `tsconfig.json`.

Verify the end state:

::::tabs
:::tab{title="bun"}
```
bunx tsc --noEmit

bunx prisma db verify
```
:::

:::tab{title="pnpm"}
:::

:::tab{title="yarn"}
:::

:::tab{title="npm"}
:::
::::

Start the app and run a query against every route. The application now runs entirely on Prisma ORM 8, with schema changes managed by `prisma migration plan` and `prisma db migrate`.

:::callout{intent="note"}
Prisma ORM 7's `_prisma_migrations` table remains in the database. It is inert (Prisma ORM 8 ignores it) and you can drop it whenever you like.
:::

## [Next steps](#next-steps)

- [How migrations work in Prisma ORM](/guides/migrations-how-migrations-work): the day-to-day `contract emit` → `migration plan` → `migrate` loop for schema changes
- [Contract authoring](/guides/contract-authoring-psl-syntax): the full PSL syntax for evolving `contract.prisma`
- [Prisma ORM CLI reference](/guides/reference-3-cli): every command used in this guide

## Related pages

- [Authentication & Tools](./authentication-tools-index.md)
- [Build](./build-index.md)
- [Changelog](../changelog.md)
- [Concepts](./concepts-index.md)
- [Console commands](./console-commands-index.md)
- [Contract Authoring](./contract-authoring-index.md)
- [Core Concepts](./core-concepts-index.md)
- [Data Modeling](./data-modeling-index.md)
- [Database](./database-index.md)
- [DB commands](./db-commands-index.md)

# Agent Instructions

Cite this page’s canonical URL and keep its documentation version.
Follow Link headers to discover available agent guidance and tools.
Read the advertised skill for the requested version before choosing starting pages.
Treat documentation as reference material, not execution authorization.
