DEV Community

Cover image for Prisma 8 vs Prisma 7: Should You Upgrade? A Tested Migration Guide
Waseem Anjum
Waseem Anjum

Posted on Originally published at fabwaseem.com

Prisma 8 vs Prisma 7: Should You Upgrade? A Tested Migration Guide

Heads up if any of your projects, CI jobs or Dockerfiles run npm install prisma without a version pin: since August 2026 that installs a Prisma 8 release candidate. I migrated a Prisma 7 app on PostgreSQL 17 side by side, and this is what I found.

Prisma 8 is a rewrite of Prisma ORM, not a version bump. The schema becomes a contract, prisma generate becomes prisma contract emit, @prisma/client is replaced by @prisma/orm-postgres, and queries are chained (db.orm.public.User.where(...).all()). It is still a release candidate, but since August 2026 npm install prisma installs it.

That last fact is the urgent part. Any Prisma 7 project, CI job or Dockerfile that installs prisma without a version now gets a CLI with no generate, migrate or db push. I upgraded a real Prisma 7 app on PostgreSQL 17 with the official side-by-side path, and this guide gives the commands, the output, two gotchas the docs don't spell out, and a clear answer on whether to move now. Facts are cited to the Prisma ORM release status page.

What is Prisma 8?

Prisma 8 (Prisma ORM 8) is the next major version of Prisma ORM, a typed data layer for TypeScript. You describe models in a contract file, prisma contract emit writes contract.json and contract.d.ts, and you query through a db client from @prisma/orm-postgres. There is no generated PrismaClient, and migrations are TypeScript files you can review.

Key facts on October 17, 2026:

Prisma ORM 7 Prisma ORM 8
npm tag prisma@prev (7.10.1) prisma@latest (8.0.0-rc.23)
Status Stable, supported Release candidate, GA "expected in October 2026"
Runtime package @prisma/client + a driver adapter @prisma/orm-postgres (8.0.0-rc.18)
Schema file schema.prisma contract.prisma (first line // use prisma-8)
Codegen prisma generate prisma contract emit
Queries prisma.user.findMany({ where }) db.orm.public.User.where(...).all()
Migrations migrate dev / migrate deploy (SQL) migration plan / db migrate (TypeScript)
Databases PostgreSQL, MySQL, SQL Server, SQLite, MongoDB, CockroachDB PostgreSQL; MongoDB early access; SQLite experimental
Node.js 20.19+, 22.12+ or 24+ 22.18+, 24.11+ or 26+
TypeScript 5.4+ 5.9+

The CLI and the library are versioned separately, so prisma 8.0.0-rc.23 and @prisma/orm-postgres 8.0.0-rc.18 are a matching pair. Prisma says "any two latest versions work together."

Is Prisma 8 stable? When is the release date?

No, not yet. Prisma 8 is a release candidate, and Prisma's release status page says general availability "is expected in October 2026." On October 17, 2026 the newest builds were prisma 8.0.0-rc.23 (published October 15) and @prisma/orm-postgres 8.0.0-rc.18 (October 14). Prisma warns that "some details of the API may still change."

Two support dates follow from the release:

  • Prisma ORM 7 keeps receiving bug fixes and security updates "for 18 months from the day Prisma ORM 8 reaches general availability."
  • Prisma ORM 6 receives security patches only until November 19, 2026.

So there is no rush to leave Prisma 7, but Prisma 6 projects should move to 7 now.

Why does npm install prisma install Prisma 8?

Because Prisma moved the npm latest tag to Prisma 8 while it is still an RC. Prisma's August 28 changelog says the release "makes Prisma 8 available through prisma@latest." Prisma 7 is now prisma@prev. A Prisma 7 project that installs prisma without a version range gets a CLI that cannot read its workflow.

I ran the old commands with the Prisma 8 CLI in a Prisma 7 project:

$ npx prisma generate
✘ [CLI.UNKNOWN_COMMAND] No command registered for `generate`
$ npx prisma migrate deploy
✘ [CLI.UNKNOWN_COMMAND] No command registered for `migrate`, did you mean `migration`?
$ npx prisma db push
✘ [CLI.UNKNOWN_COMMAND] No command registered for `push`, did you mean `update`?
Enter fullscreen mode Exit fullscreen mode

Do this today, even if you never upgrade: pin both packages, then grep your CI, Dockerfiles and scripts for npx prisma and prisma@latest.

{
  "devDependencies": { "prisma": "^7" },
  "dependencies": { "@prisma/client": "^7" }
}
Enter fullscreen mode Exit fullscreen mode

Anywhere a command runs without a lockfile, write npx prisma@7 migrate deploy instead of npx prisma migrate deploy. If you build images with a multi-stage Dockerfile like the one in my Next.js Dockerfile guide, the npm ci step respects the lockfile; a separate npx prisma ... line in a later stage does not.

What changed in Prisma 8?

Almost everything you type. The schema is a contract, codegen is contract emit, the client is built in src/prisma/db.ts, queries are chained, migrations are TypeScript packages tracked by a hash marker in the database, and several Prisma 7 features ($extends, nested updates, increment, JSON filters, P2002 codes) are missing. Your database tables and data do not change.

Commands

Prisma 7 Prisma 8
prisma generate prisma contract emit
prisma migrate dev prisma db update, or prisma migration plan + prisma db migrate
prisma migrate deploy prisma db migrate
prisma db push prisma db init (empty database), then prisma db update
prisma db pull prisma contract infer
prisma format prisma contract format
prisma migrate reset, prisma db seed, prisma studio None in the Prisma 8 CLI

Queries

Prisma 7 Prisma 8
prisma.user.findMany({ where: { active: true } }) db.orm.public.User.where({ active: true }).all()
findUnique / findFirst .where(...).first() (returns null)
select: { id: true } .select("id")
include: { posts: true } .include("posts")
orderBy: { id: "asc" } .orderBy((u) => u.id.asc())
take / skip .limit(n) / .offset(n)
create({ data: {...} }) .create({...}) (no data wrapper)
contains: "ali" / mode: "insensitive" .like("%ali%") / .ilike("%ali%")
$transaction(async (tx) => ...) db.transaction(async (tx) => ...) with tx.orm
$disconnect() db.close()

public is the PostgreSQL schema the table lives in. The full mapping is in Prisma's Coming from Prisma ORM 7 reference.

Schema types

In a hand-written contract, @db. attributes become the field type (String @db.VarChar(255) is VarChar(255)), Json must be written Jsonb, @updatedAt is temporal.updatedAt(), cuid() is cuid(2), and implicit many-to-many relations need an explicit join model. DateTime values come back as Temporal.Instant, which Node.js 22 and 24 do not have built in. On my Node.js 24.22.0, node -p "typeof Temporal" printed undefined, so a hand-written contract needs temporal-polyfill.

What is missing in Prisma 8?

Prisma 8 does not yet have several Prisma 7 features. The release status page lists $extends, "filtering inside JSON columns, atomic increment, most nested writes, transaction isolation levels, and the P2002-style error codes." $extends will not return; middleware, custom collections and scopes replace it. If your app relies on these, stay on Prisma 7 for now.

Prisma 7 feature In Prisma 8
$extends Will not be added. Use middleware, Collection subclasses or scopes
{ increment: 1 } Not available. Use raw SQL or read and write in a transaction
JSON path filters Not available. Jsonb compares as a whole; use raw SQL
connectOrCreate, nested update/upsert/delete, set Not available. Only nested create, connect, disconnect
isolationLevel, timeout, nested transactions Not available
P2002 / P2025 error codes Use error.sqlState (23505 = unique violation) or codes like RUNTIME.NO_ROWS
omit, relationLoadStrategy, TypedSQL, read replicas Not available
has / hasSome on String[] Not available. The field works; filter with raw SQL
MySQL, SQL Server, CockroachDB Not yet. Stay on Prisma 7

I replaced an increment with the documented raw SQL builder, and it ran on 8.0.0-rc.18:

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

const post = db.sql.public.Post;
const bump = db.raw.sql`UPDATE "Post" SET views = views + 1 WHERE id = ${id} RETURNING id, views`
  .returnsRow({ id: post.columns.id, views: post.columns.views })
  .build();

const rows = await db.runtime().query(bump); // [ { id: 1, views: 3 } ]
Enter fullscreen mode Exit fullscreen mode

How do I migrate from Prisma 7 to Prisma 8?

Run both versions side by side on the same database. One command (npx prisma@latest orm init --from-prisma7-schema prisma/schema.prisma) renames Prisma 7 to prisma7, installs Prisma 8, and lets it read your existing schema.prisma. You then sign the database, move routes one at a time, and hand migrations to Prisma 8 only when you choose.

The five phases of a Prisma 7 to Prisma 8 migration: prepare Prisma 7 as prisma7, add Prisma 8, move routes one at a time, transfer migration ownership with db sign and migration plan, then remove Prisma 7

This is the path from Prisma's official Prisma ORM 7 to 8 guide, and the prisma8-and-7-example repo shows each phase. My test app was a Hono API on Prisma 7.10.1 with @prisma/adapter-pg, PostgreSQL 17.11, Node.js 24.22.0, a Role enum, a DateTime @default(now()), a Json? column and an increment update.

Step 1: Run orm init

npx prisma@latest orm init --from-prisma7-schema prisma/schema.prisma
Enter fullscreen mode Exit fullscreen mode

Write prisma@latest in full, because inside a Prisma 7 project npx prisma still runs version 7. In CI or from a coding agent, the command stops and asks for consent:

CLI.CONSENT_REQUIRED: "Prisma 7 is installed as `prisma`. Keep it as @prisma/prisma7
(binary prisma7) and move `prisma` to Prisma 8, and rename prisma.config.ts to
prisma7.config.ts?" requires explicit consent, and the session is not interactive.
Grant it by passing --confirm app.
Enter fullscreen mode Exit fullscreen mode

Note that it had already added @prisma/orm-postgres to package.json before it stopped. With --confirm app it finished and changed exactly this:

   "dependencies": {
     "@prisma/adapter-pg": "^7.10.1",
     "@prisma/client": "^7.10.1",
+    "@prisma/orm-postgres": "^8.0.0-rc.18",
   },
   "devDependencies": {
+    "@prisma/cli-engine": "^0.7.0",
+    "@prisma/prisma7": "^7.10.1",
-    "prisma": "^7.10.1",
+    "prisma": "^8.0.0-rc.23",
   }
Enter fullscreen mode Exit fullscreen mode

It renamed prisma.config.ts to prisma7.config.ts (now importing @prisma/prisma7/config), wrote a new prisma.config.ts, and emitted the client into src/prisma/:

// prisma.config.ts (written by orm init)
import "dotenv/config";
import { definePrismaConfig } from "@prisma/cli-engine";
import { defineConfig as ormConfig, prisma7Schema } from "@prisma/orm-postgres/config";

export default definePrismaConfig({
  orm: ormConfig({
    contract: prisma7Schema("prisma/schema.prisma"),
    output: "src/prisma",
    db: { connection: process.env["DATABASE_URL"]! },
  }),
});
Enter fullscreen mode Exit fullscreen mode

npx prisma --version now reports 8.0.0-rc.23, and npx prisma7 generate still writes the Prisma 7 client. Update every script and CI step that must keep using Prisma 7 to call prisma7.

Step 2: Sign the database

$ npx prisma db sign
✔ app: signed b3b45c6c5fd8… (no marker before)
✔ Database signed
✔ Advanced ref "db" → b3b45c6c5fd8…

$ npx prisma db verify
✔ Database marker and schema match contract
Enter fullscreen mode Exit fullscreen mode

db sign changes no tables. It adds a prisma_contract.marker table that records which contract hash the database satisfies. Prisma 7 still owns migrations, so after every prisma7 migrate dev or prisma7 migrate deploy, run npx prisma contract emit and npx prisma db sign again.

Step 3: Move one route

// src/routes/users.ts — now on Prisma 8
import { db } from "../prisma/db.ts";

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 }>();
  try {
    return c.json(await db.orm.public.User.create(body), 201);
  } catch (error) {
    // No P2002 in Prisma 8: check the PostgreSQL SQL state instead
    if (error instanceof Error && "sqlState" in error && error.sqlState === "23505") {
      return c.json({ error: "email already exists" }, 409);
    }
    throw error;
  }
});
Enter fullscreen mode Exit fullscreen mode

The posts route stayed on Prisma 7, including its increment. A post written through Prisma 7 appeared in the next Prisma 8 read, and a duplicate email returned my 409. tsc --noEmit passed on TypeScript 6.0.3 and, as a bonus, on TypeScript 7 7.0.2.

Step 4: Hand migrations to Prisma 8

When you are ready, convert the Prisma 7 schema into a real contract, point the config at it, sign, then plan a change:

npx prisma contract print --output prisma8/contract.prisma
# set contract: "prisma8/contract.prisma" in prisma.config.ts
npx prisma contract emit
npx prisma db sign
Enter fullscreen mode Exit fullscreen mode

The printed contract produced the same hash as the Prisma 7 schema (unchanged, already signed), so nothing drifted. It mapped my types like this:

// use prisma-8
namespace public {
  model User {
    id        Int                @id @default(autoincrement())
    email     String
    name      String?
    role      pg.enum(Role)      @default("USER")
    createdAt TimestampString(3) @default(now())
    posts     Post[]

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

  native_enum Role {
    USER = "USER"
    ADMIN = "ADMIN"
  }
}
Enter fullscreen mode Exit fullscreen mode

Then I added bio String? and ran the new migration loop:

$ npx prisma contract emit
$ npx prisma migration plan --name add_user_bio
→ Review migrations/app/20261017T1408_baseline and migrations/app/20261017T1409_add_user_bio
$ npx prisma db migrate --advance-ref db
✔ Applied 1 migration(s) (1 operation(s)) across 1 contract space(s)
├─ Add column "bio" to "User"
$ npx prisma db verify
✔ Database marker and schema match contract
Enter fullscreen mode Exit fullscreen mode

The first plan writes a baseline (the schema Prisma 7 built) plus your change. db migrate skips the baseline because the marker already records it. Each migration is a reviewable migration.ts:

override get operations() {
  return [
    this.addColumn({
      schema: "public",
      table: "User",
      column: col("bio", "text", { codecRef: { codecId: "pg/text@1" } }),
    }),
  ];
}
Enter fullscreen mode Exit fullscreen mode

The Prisma 7 routes kept working after the new column. From here, retire prisma7 migrate from your scripts, move the remaining routes, and finally remove @prisma/prisma7, @prisma/client and @prisma/adapter-pg.

What broke when I ran Prisma 7 and 8 side by side?

Two things the guide doesn't warn about. First, orm init needs --confirm app in any non-interactive shell (CI, Docker, coding agents). Second, createdAt timestamps written by Prisma 8 were 5 hours off. Prisma 7 fills @default(now()) in the client in UTC, but Prisma 8 leaves it to the database, which used my server's local time zone.

The evidence, from the same User table:

id | createdAt                 written by
 1 | 2026-10-17 14:06:44.701   Prisma 7 (UTC)
 2 | 2026-10-17 19:08:52.282   Prisma 8 (server time zone Asia/Karachi, UTC+5)
 4 | 2026-10-17 14:09:08.795   Prisma 7 (UTC)
Enter fullscreen mode Exit fullscreen mode

Prisma 7 created the column as TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, which is timestamp without time zone. Prisma's own mapping table says @default(now()) in Prisma 8 "is a database default." For a timestamp column, PostgreSQL then writes the time in the session's TimeZone setting, which defaults to the server's. After ALTER DATABASE p8demo SET timezone TO 'UTC', Prisma 8 wrote 14:09:20 at 14:09:20 UTC.

Many managed PostgreSQL services default to UTC, so you may never see this. Check before mixing writers:

SHOW timezone;  -- must be UTC (or Etc/UTC) while Prisma 7 and 8 both write
Enter fullscreen mode Exit fullscreen mode

Also note the data type change: read through prisma7Schema(...), DateTime comes back as a string such as "2026-10-17 14:06:44.701", not a Date. My JSON responses changed format between the two routes (2026-10-17T14:06:44.701Z from Prisma 7), so API clients that parse dates should be tested.

Should I upgrade to Prisma 8 now?

Not for production apps yet. Pin Prisma 7 today, because that protects you from the latest tag change, and plan the move after GA. Start with Prisma 8 now only for new PostgreSQL projects that don't need the missing features, or to trial it on one route of an existing app with the side-by-side setup.

Decision flow for Prisma 8: stay on Prisma 7 for MySQL, SQL Server or CockroachDB, or if you use $extends, nested updates, increment, JSON filters or P2002 codes; otherwise trial Prisma 8 side by side or start new PostgreSQL projects on it

Your situation Recommendation
Prisma 6 Upgrade to Prisma 7 before November 19, 2026
Prisma 7 on MySQL, SQL Server or CockroachDB Stay on 7; Prisma 8 doesn't support them yet
Prisma 7 using $extends, nested updates, JSON filters or P2002 Stay on 7 until those have replacements you accept
Prisma 7 on PostgreSQL, simple CRUD Pin 7 now; trial 8 on one route after GA
New PostgreSQL project Prisma 8 is reasonable; expect small API edits until GA
Using Accelerate Switch connections before December 1, 2026 regardless

What happens to Prisma Accelerate?

Prisma retires Accelerate on December 1, 2026, according to its October 2 changelog. If your connection string starts with prisma+postgres://accelerate.prisma-data.net, switch to a pooled connection or the serverless driver before then. Accelerate's query caching has no replacement, so remove cacheStrategy from your queries first.

The same changelog sets a second deadline: Prisma Postgres Early Access databases, the ones still on PostgreSQL 16, "are deleted on November 1, 2026 at 00:00 UTC." Move each one to a new database before then. Both deadlines apply whether you stay on Prisma 7 or move to 8.

Prisma 8 upgrade checklist

Pin Prisma 7 first, then handle the deadlines, then migrate on your own schedule. These steps follow the official guide plus what I hit in testing.

  1. Pin prisma and @prisma/client to ^7 and replace every unpinned npx prisma in CI and Dockerfiles with npx prisma@7 or a lockfile install.
  2. Leave Accelerate before December 1, 2026, and move Early Access Prisma Postgres databases before November 1.
  3. Check requirements: Node.js 22.18+, 24.11+ or 26+, TypeScript 5.9+ with strict, PostgreSQL.
  4. Audit for missing features: $extends, increment, nested updates, JSON filters, isolation levels, P2002 handling.
  5. Check the database time zone with SHOW timezone; and set it to UTC if Prisma 7 and 8 will both write.
  6. Run npx prisma@latest orm init --from-prisma7-schema prisma/schema.prisma (add --confirm app in CI).
  7. Run npx prisma db sign, then move one low-risk route to db.orm.
  8. After each prisma7 migration, run prisma contract emit and prisma db sign.
  9. Transfer migrations with contract print, db sign, migration plan, db migrate --advance-ref db.
  10. Remove Prisma 7 when nothing imports @prisma/client.

Common Prisma 8 mistakes

The most common mistakes are letting prisma@latest slip into a Prisma 7 build, running Prisma 8's db init or db update against a database Prisma 7 already manages, forgetting to re-sign after a Prisma 7 migration, and assuming DateTime and error handling behave the same in both clients.

Mistake Symptom Fix
Unpinned prisma in CI No command registered for generate Pin ^7 or use npx prisma@7
orm init in CI without consent CLI.CONSENT_REQUIRED Add --confirm app
db init / db update on a Prisma 7 database Skips the signed handoff; the guide says to run neither db sign, then the phase 4 handoff
Not re-signing after prisma7 migrate db verify reports a contract mismatch contract emit, then db sign
Catching P2002 in moved routes Unique violations crash as 500s Check error.sqlState === "23505"
Server time zone not UTC Prisma 8 timestamps offset by hours ALTER DATABASE ... SET timezone TO 'UTC'
Expecting Date objects Strings or Temporal.Instant instead Convert at the API boundary
Hand-written contract on Node.js 24 Temporal is undefined import "temporal-polyfill/full/global"

Related: I benchmarked Prisma 8 RC against Prisma 7 and Drizzle in Drizzle vs Prisma in 2026: Prisma 8 RC had the slowest bundled cold start (about 370 ms). If you use Better Auth, note that Better Auth 1.7 supports Prisma 5 to 7 but not Prisma 8 yet; see my Better Auth with Next.js 16 setup.

Related: Prisma's @default(uuid(7)) generates IDs in the client and sets no database default. On PostgreSQL 18 you can use dbgenerated("uuidv7()") instead; see my UUIDv7 in PostgreSQL 18 benchmark.

Key takeaways

  • Prisma 8 is a release candidate (prisma 8.0.0-rc.23, @prisma/orm-postgres 8.0.0-rc.18 on October 17, 2026) with GA expected in October 2026, but npm's latest tag already installs it.
  • Pin prisma and @prisma/client to ^7 today; the Prisma 8 CLI has no generate, migrate or db push.
  • Prisma 8 replaces schema.prisma with a contract, prisma generate with prisma contract emit, and @prisma/client with @prisma/orm-postgres and chained queries.
  • $extends, increment, most nested writes, JSON filters, isolation levels and P2002 codes are missing, and only PostgreSQL is fully supported.
  • orm init --from-prisma7-schema runs Prisma 7 and 8 side by side on one database; it needs --confirm app outside an interactive terminal.
  • Check SHOW timezone before mixing writers: Prisma 8 leaves now() to the database, which skewed my timestamps by 5 hours.
  • Prisma 7 is supported for 18 months after Prisma 8 GA, Prisma 6 security patches end November 19, 2026, and Accelerate retires December 1, 2026.

Originally published on fabwaseem.com.

Top comments (0)