Mongoose
This guide shows you how to migrate an application from Mongoose to Prisma ORM. The migration is gradual and runs against the database you already have: you add Prisma ORM next to Mongoose, describe the collections Mongoose manages in a contract, replace queries route by route, and remove Mongoose when nothing calls it anymore. Your data never moves.
The examples use a small Express API with three Mongoose models (User, Category, Post) and four routes. Every command, code block, and response below was run end to end against a local MongoDB replica set.
- A Mongoose project you want to migrate (this guide uses TypeScript and Express)
- Node.js 24 or later
- A MongoDB replica set. MongoDB Atlas is one already; locally, start
mongodwith--replSet rs0and runrs.initiate()once - Basic familiarity with Mongoose
To delegate this guide to your coding agent, copy the prompt below and hand it over:
Migrate this Mongoose app to Prisma ORM against the same MongoDB database, following https://www.prisma.io/docs/guides/switch-to-prisma-orm/from-mongoose.md.
1. Run `npx prisma@latest orm init --target mongodb` in the project root, choose PSL, then run `npx prisma@latest init` so the Prisma agent skills are installed, and use them. Make sure `.env` has DATABASE_URL pointing at the database the Mongoose app already uses.
2. `contract infer` does not support MongoDB. Write `src/prisma/contract.prisma` by hand from the Mongoose schemas: one model per collection with `id ObjectId @id @map("_id")` and `@@map("<collection>")`, a `type` block per nested schema, `ObjectId` plus `@relation` for each `ref`, and `ObjectId[]` for arrays of refs. Then run `npx prisma contract emit`.
3. Replace the Mongoose queries route by route with `db.orm.<collection>` calls and `.include(...)` for `populate` on single references. For `populate` on arrays of references, use the pipeline builder with `.lookup(...)`. Run the app and verify every route with curl before and after.
4. When no code imports mongoose: run `npm uninstall mongoose && npm install mongodb`, remove the `__v` field from all documents, preview the validators with `npx prisma db update --dry-run`, apply the previewed collMod statements, then run `npx prisma db sign` and `npx prisma db verify`.The steps for migrating from Mongoose to Prisma ORM are the same for any application, whether it is a REST API with Express, a GraphQL server, or a background worker:
- Add Prisma ORM to the project with
orm init - Describe the collections Mongoose manages in a contract and emit it
- Replace Mongoose queries with Prisma ORM queries, one route at a time
- Remove Mongoose and let Prisma ORM own the schema
Prisma ORM does not generate a client. There is no prisma generate step and no schema.prisma: contract emit writes the contract to contract.json and contract.d.ts, and your app reads those two files.
The app has three Mongoose models. User has a nested profile, Post references a User and an array of Category documents:
import { Schema, model } from "mongoose";
const userSchema = new Schema({
email: { type: String, required: true, unique: true },
name: { type: String, required: true },
profile: { bio: String },
});
export const User = model("User", userSchema);import { Schema, model } from "mongoose";
const postSchema = new Schema({
title: { type: String, required: true },
content: { type: String, required: true },
published: { type: Boolean, default: false },
author: { type: Schema.Types.ObjectId, ref: "User", required: true },
categories: [{ type: Schema.Types.ObjectId, ref: "Category" }],
});
export const Post = model("Post", postSchema);The Express server exposes GET /users, GET /users/:id, POST /users, and GET /posts, which populates author and categories:
import express from "express";
import mongoose from "mongoose";
import { User } from "./models/user";
import { Post } from "./models/post";
import "./models/category";
await mongoose.connect(process.env.DATABASE_URL!);
const app = express();
app.use(express.json());
app.get("/users", async (_req, res) => {
const users = await User.find().sort({ name: 1 });
res.json(users);
});
app.get("/users/:id", async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) return res.status(404).json({ error: "Not found" });
res.json(user);
});
app.post("/users", async (req, res) => {
const user = await User.create({ email: req.body.email, name: req.body.name });
res.status(201).json(user);
});
app.get("/posts", async (_req, res) => {
const posts = await Post.find({ published: true }).populate("author").populate("categories");
res.json(posts);
});
const port = Number(process.env.PORT ?? 3000);
app.listen(port, () => console.log(`Listening on http://localhost:${port}`));The database is seeded with two users, two categories, and three posts. With the Mongoose server running, GET /users returns:
[{"profile":{"bio":"Writes about databases"},"_id":"6aa2cd2ed97f8d7357dc9bd1","email":"alice@prisma.io","name":"Alice","__v":0},{"_id":"6aa2cd2ed97f8d7357dc9bd2","email":"bob@prisma.io","name":"Bob","__v":0}]Keep this response to compare against later, because the Prisma ORM version of the route returns the same documents.
Run orm init in the project root. It adds Prisma ORM to the existing app; it does not scaffold a new one:
bunx prisma@latest orm init --target mongodbpnpm dlx prisma@latest orm init --target mongodbyarn dlx prisma@latest orm init --target mongodbnpx prisma@latest orm init --target mongodbChoose PSL when asked for the contract authoring style and keep the default schema path. The command installs the packages, writes the Prisma ORM files, and emits a starter contract:
✔ npm add @prisma/orm-mongo dotenv
✔ npm add -D prisma@latest
✔ npm add -D @prisma/cli-engine@0.6.1
✔ Emit the contract
│ target: mongodb
│ authoring: psl
│ schema: src/prisma/contract.prisma
written
├─ src/prisma/contract.prisma
├─ prisma.config.ts
├─ src/prisma/db.ts
├─ prisma-8.md
├─ .env.example
├─ tsconfig.json
├─ .gitignore
├─ .gitattributes
└─ package.json
installed
├─ @prisma/orm-mongo
├─ dotenv
├─ prisma@latest (dev)
└─ @prisma/cli-engine@0.6.1 (dev)
✔ Done. Open prisma-8.md to get started.@prisma/orm-mongo is the library your app imports to query MongoDB. It uses the mongodb driver, the same one Mongoose already depends on, so you do not need a second database driver.
orm init also changes the module settings in tsconfig.json, and adds "type": "module" to package.json when the file has no "type" field. If your app is CommonJS, follow In a CommonJS project before you start the server again.
Install the Prisma agent skills for your coding agent with init, which also adds a postinstall hook that resyncs them on every install:
Choose PSL when asked for the contract authoring style and keep the default schema path. The command installs the packages, writes the Prisma ORM files, and emits a starter contract:
✔ npm add @prisma/orm-mongo dotenv
✔ npm add -D prisma@latest
✔ npm add -D @prisma/cli-engine@0.6.1
✔ Emit the contract
│ target: mongodb
│ authoring: psl
│ schema: src/prisma/contract.prisma
written
├─ src/prisma/contract.prisma
├─ prisma.config.ts
├─ src/prisma/db.ts
├─ prisma-8.md
├─ .env.example
├─ tsconfig.json
├─ .gitignore
├─ .gitattributes
└─ package.json
installed
├─ @prisma/orm-mongo
├─ dotenv
├─ prisma@latest (dev)
└─ @prisma/cli-engine@0.6.1 (dev)
✔ Done. Open prisma-8.md to get started.@prisma/orm-mongo is the library your app imports to query MongoDB. It uses the mongodb driver, the same one Mongoose already depends on, so you do not need a second database driver.
orm init also changes the module settings in tsconfig.json, and adds "type": "module" to package.json when the file has no "type" field. If your app is CommonJS, follow In a CommonJS project before you start the server again.
Install the Prisma agent skills for your coding agent with init, which also adds a postinstall hook that resyncs them on every install:
bunx --bun prisma@latest initpnpm dlx prisma@latest inityarn dlx prisma@latest initnpx prisma@latest initIf a later CLI command reports that the skills are out of date, run npx prisma skills sync.
orm init wrote a prisma.config.ts that loads .env and points at the contract:
import 'dotenv/config';
import { definePrismaConfig } from '@prisma/cli-engine';
import { defineConfig as ormConfig } from '@prisma/orm-mongo/config';
export default definePrismaConfig({
orm: ormConfig({
contract: "./src/prisma/contract.prisma",
db: {
connection: process.env['DATABASE_URL']!,
},
}),
});It also wrote src/prisma/db.ts, the client the rest of the app imports. There is no adapter and no engine; the factory takes the emitted contract and the connection string:
import 'dotenv/config';
import mongo from '@prisma/orm-mongo/runtime';
import type { Contract } from './contract.d';
import contractJson from './contract.json' with { type: 'json' };
export const db = mongo<Contract>({
contractJson,
url: process.env['DATABASE_URL']!,
});Both read DATABASE_URL, so point it at the database your Mongoose app already uses. orm init writes an .env.example but never touches an existing .env:
DATABASE_URL="mongodb://localhost:27017/blog?replicaSet=rs0&directConnection=true"2. Describe your collections in a contract
Section titled “2. Describe your collections in a contract”On PostgreSQL, contract infer reads the live schema and writes a starter contract. MongoDB has no schema to read, so the command stops:
bunx prisma contract infer --output ./src/prisma/contract.prismapnpm prisma contract infer --output ./src/prisma/contract.prismayarn prisma contract infer --output ./src/prisma/contract.prismanpx prisma contract infer --output ./src/prisma/contract.prisma✘ [CONTRACT.INFER_UNSUPPORTED] contract infer is not supported for this family
why: The configured family does not implement the PslContractInferCapable capability, so an inferred PSL contract cannot be produced from the live database schema.Your Mongoose schemas are the source of truth instead. Translate them with these rules:
| Mongoose | Prisma ORM contract |
|---|---|
model("User", schema) stores in users | model User { ... @@map("users") } |
_id (implicit) | id ObjectId @id @map("_id") |
{ type: String, required: true } | name String |
{ type: String } | name String? |
{ type: String, unique: true } | email String @unique |
nested schema profile: { bio: String } | type Profile { bio String? } and profile Profile? |
{ type: ObjectId, ref: "User" } | authorId ObjectId @map("author") plus author User @relation(fields: [authorId], references: [id]) |
[{ type: ObjectId, ref: "Category" }] | categoryIds ObjectId[] @map("categories") |
Mongoose stores a model named User in the users collection, so every model needs @@map with the pluralized, lowercased collection name. Replace the starter contract with the models from the example app:
// use prisma-8
type Profile {
bio String?
}
model User {
id ObjectId @id @map("_id")
email String @unique
name String
profile Profile?
posts Post[]
@@map("users")
}
model Category {
id ObjectId @id @map("_id")
name String
@@map("categories")
}
model Post {
id ObjectId @id @map("_id")
title String
content String
published Bool
author User @relation(fields: [authorId], references: [id])
authorId ObjectId @map("author")
categoryIds ObjectId[] @map("categories")
@@map("posts")
}Before you go on, check these differences from Mongoose:
- Prisma ORM reads and writes MongoDB fields by their stored names.
authorId @map("author")is exposed by the query API asauthor, the key Mongoose stores, and.include("author")fills it with the user document the waypopulate("author")does.db.orm.posts.where({ authorId })is a type error. - Mongoose adds a
__vversion key to every document. It is not in the contract and Prisma ORM passes it through on reads. You remove it in step 4, before Prisma ORM adds validators.
You do not have to model every collection on day one. Start with the ones the routes you migrate first touch.
✘ [CONTRACT.INFER_UNSUPPORTED] contract infer is not supported for this family
why: The configured family does not implement the PslContractInferCapable capability, so an inferred PSL contract cannot be produced from the live database schema.Your Mongoose schemas are the source of truth instead. Translate them with these rules:
| Mongoose | Prisma ORM contract |
|---|---|
model("User", schema) stores in users |
model User { ... @@map("users") } |
_id (implicit) |
id ObjectId @id @map("_id") |
{ type: String, required: true } |
name String |
{ type: String } |
name String? |
{ type: String, unique: true } |
email String @unique |
nested schema profile: { bio: String } |
type Profile { bio String? } and profile Profile? |
{ type: ObjectId, ref: "User" } |
authorId ObjectId @map("author") plus author User @relation(fields: [authorId], references: [id]) |
[{ type: ObjectId, ref: "Category" }] |
categoryIds ObjectId[] @map("categories") |
Mongoose stores a model named User in the users collection, so every model needs @@map with the pluralized, lowercased collection name. Replace the starter contract with the models from the example app:
// use prisma-8
type Profile {
bio String?
}
model User {
id ObjectId @id @map("_id")
email String @unique
name String
profile Profile?
posts Post[]
@@map("users")
}
model Category {
id ObjectId @id @map("_id")
name String
@@map("categories")
}
model Post {
id ObjectId @id @map("_id")
title String
content String
published Bool
author User @relation(fields: [authorId], references: [id])
authorId ObjectId @map("author")
categoryIds ObjectId[] @map("categories")
@@map("posts")
}Before you go on, check these differences from Mongoose:
- Prisma ORM reads and writes MongoDB fields by their stored names.
authorId @map("author")is exposed by the query API asauthor, the key Mongoose stores, and.include("author")fills it with the user document the waypopulate("author")does.db.orm.posts.where({ authorId })is a type error. - Mongoose adds a
__vversion key to every document. It is not in the contract and Prisma ORM passes it through on reads. You remove it in step 4, before Prisma ORM adds validators.
You do not have to model every collection on day one. Start with the ones the routes you migrate first touch.
Turn the contract into the artifacts the runtime and the CLI read:
bunx prisma contract emitpnpm prisma contract emityarn prisma contract emitnpx prisma contract emit✔ Resolving contract source...
✔ Emitting contract...
│ contract: src/prisma/contract.json
│ types: src/prisma/contract.d.ts
✔ Emitted contract.json and contract.d.tsRun this again whenever you edit contract.prisma. Nothing else needs regenerating.
✔ Resolving contract source...
✔ Emitting contract...
│ contract: src/prisma/contract.json
│ types: src/prisma/contract.d.ts
✔ Emitted contract.json and contract.d.tsRun this again whenever you edit contract.prisma. Nothing else needs regenerating.
3. Replace Mongoose queries with Prisma ORM
Section titled “3. Replace Mongoose queries with Prisma ORM”Prisma ORM addresses collections by their storage name (db.orm.users, not db.orm.User) and chains a filter before every read or write except create:
// Find many, sorted
const users = await User.find().sort({ name: 1 });
// Find one by id
const user = await User.findById(id);
// Find one by field
const alice = await User.findOne({ email: "alice@prisma.io" });
// Create
const user = await User.create({ email: "alice@prisma.io", name: "Alice" });
// Update one
await User.findByIdAndUpdate(id, { name: "New name" });
// Delete one
await User.findByIdAndDelete(id);
// Populate a single reference
const posts = await Post.find({ published: true }).populate("author");// Find many, sorted (1 ascending, -1 descending)
const users = await db.orm.users.orderBy({ name: 1 }).all();
// Find one by id
const user = await db.orm.users.where({ _id: id }).first();
// Find one by field
const alice = await db.orm.users.where({ email: "alice@prisma.io" }).first();
// Create; returns the inserted document with its _id
const user = await db.orm.users.create({ email: "alice@prisma.io", name: "Alice", profile: null });
// Update one; returns the updated document
await db.orm.users.where({ _id: id }).update({ name: "New name" });
// Delete one
await db.orm.users.where({ _id: id }).delete();
// Populate a single reference
const posts = await db.orm.posts.where({ published: true }).include("author").all();.where(...) takes a plain object matched by equality. .update(...) and .delete() act on one document; use .updateAll(...) and .deleteAll() for many. Optional fields such as profile are required in the create input, so pass null when there is no value. For filters the object form does not cover, and for anything Mongoose did with aggregate, use the pipeline builder.
.where(...) takes a plain object matched by equality. .update(...) and .delete() act on one document; use .updateAll(...) and .deleteAll() for many. Optional fields such as profile are required in the create input, so pass null when there is no value. For filters the object form does not cover, and for anything Mongoose did with aggregate, use the pipeline builder.
.include(...) covers a single reference, but populate("categories") on an array of ids has no .include equivalent yet, so express it as a pipeline: .lookup(...) is a typed $lookup, and MongoDB matches an array on the local side against _id on the foreign side. Adding .unwind("author") after a lookup on a single reference turns the one-element array $lookup produces into the object Mongoose returns:
const runtime = await db.runtime();
const plan = db.query
.from("posts")
.match((f) => f.published.eq(true))
.lookup((from) =>
from("users")
.on((post, user) => ({ local: post.author, foreign: user._id }))
.as("author"),
)
.unwind("author")
.lookup((from) =>
from("categories")
.on((post, category) => ({ local: post.categories, foreign: category._id }))
.as("categories"),
)
.build();
const posts = await runtime.query(plan);Run read plans with runtime.query(plan), because runtime.execute(plan) is for write plans and rejects an aggregate command.
Replace the Mongoose imports and the mongoose.connect call with the db client, then rewrite each handler. The finished server:
import express from "express";
import { db } from "./prisma/db";
const app = express();
app.use(express.json());
app.get("/users", async (_req, res) => {
const users = await db.orm.users.orderBy({ name: 1 }).all();
res.json(users);
});
app.get("/users/:id", async (req, res) => {
const user = await db.orm.users.where({ _id: req.params.id }).first();
if (!user) return res.status(404).json({ error: "Not found" });
res.json(user);
});
app.post("/users", async (req, res) => {
const user = await db.orm.users.create({
email: req.body.email,
name: req.body.name,
profile: req.body.bio ? { bio: req.body.bio } : null,
});
res.status(201).json(user);
});
app.get("/posts", async (_req, res) => {
const runtime = await db.runtime();
const plan = db.query
.from("posts")
.match((f) => f.published.eq(true))
.lookup((from) =>
from("users")
.on((post, user) => ({ local: post.author, foreign: user._id }))
.as("author"),
)
.unwind("author")
.lookup((from) =>
from("categories")
.on((post, category) => ({ local: post.categories, foreign: category._id }))
.as("categories"),
)
.build();
const posts = await runtime.query(plan);
res.json(posts);
});
const port = Number(process.env.PORT ?? 3000);
app.listen(port, () => console.log(`Listening on http://localhost:${port}`));There is no connect call: the client connects on the first query. Do not call db.close() in a handler; the connection pool is shared across requests and closes with the process.
Start the server and check the routes:
curl http://localhost:3000/users[{"profile":{"bio":"Writes about databases"},"__v":0,"_id":"6aa2cd2ed97f8d7357dc9bd1","email":"alice@prisma.io","name":"Alice"},{"__v":0,"_id":"6aa2cd2ed97f8d7357dc9bd2","email":"bob@prisma.io","name":"Bob"}]The same documents Mongoose returned, __v included, because both read the same collection.
curl http://localhost:3000/posts[{"_id":"6aa2cd2ed97f8d7357dc9bd5","title":"Hello MongoDB","content":"First post","published":true,"author":{"_id":"6aa2cd2ed97f8d7357dc9bd1","email":"alice@prisma.io","name":"Alice","profile":{"bio":"Writes about databases"},"__v":0},"categories":[{"_id":"6aa2cd2ed97f8d7357dc9bd3","name":"Databases","__v":0}],"__v":0},{"_id":"6aa2cd2ed97f8d7357dc9bd7","title":"Bob's post","content":"Hi","published":true,"author":{"_id":"6aa2cd2ed97f8d7357dc9bd2","email":"bob@prisma.io","name":"Bob","__v":0},"categories":[],"__v":0}]curl -X POST http://localhost:3000/users \
-H "content-type: application/json" \
-d '{"email":"carol@prisma.io","name":"Carol"}'{"_id":"6aa2d3fcc0a1a4429529ba2c","email":"carol@prisma.io","name":"Carol","profile":null}.create(...) returns the inserted document with its server-assigned _id, so the handler needs no second query.
You do not have to migrate every route in one go. Mongoose and Prisma ORM can read and write the same collections while you work through the app: Prisma ORM reads Mongoose documents as shown above, and Mongoose reads documents Prisma ORM created. After the POST /users call above, the untouched Mongoose GET /users route returns Carol too, without a __v key:
[{"profile":{"bio":"Writes about databases"},"_id":"6aa2cd2ed97f8d7357dc9bd1","email":"alice@prisma.io","name":"Alice","__v":0},{"_id":"6aa2cd2ed97f8d7357dc9bd2","email":"bob@prisma.io","name":"Bob","__v":0},{"profile":null,"_id":"6aa2d3fcc0a1a4429529ba2c","email":"carol@prisma.io","name":"Carol"}]Hold off on db update, db sign, and migrations until step 4. They add strict validators to the collections, and Mongoose writes stop passing them.
When no module imports mongoose, remove it and keep the driver Prisma ORM needs. mongodb was a transitive dependency of Mongoose; make it a direct one:
bun remove mongoose
bun add mongodbpnpm remove mongoose
pnpm add mongodbyarn remove mongoose
yarn add mongodbnpm uninstall mongoose
npm install mongodbDelete the models/ directory and any mongoose.connect call that is left.
Mongoose wrote __v on every document. The validators Prisma ORM adds in the next step reject fields the contract does not declare, so strip it first. In mongosh, against your database:
for (const c of ["users", "posts", "categories"]) {
const r = db[c].updateMany({}, { $unset: { __v: "" } });
print(c, r.modifiedCount);
}users 2
posts 3
categories 2If you cannot do this yet, add v Int? @map("__v") to every model instead and drop it later. The field must be optional, because documents Prisma ORM creates have no __v key.
Prisma ORM tracks a database by a signature and, on MongoDB, by a $jsonSchema validator per collection. Preview what it wants to apply:
bunx prisma db update --dry-runpnpm prisma db update --dry-runyarn prisma db update --dry-runnpx prisma db update --dry-run✔ Planned 3 operation(s) across 1 contract space
App space
├─ ⚠ Add validator on categories
├─ ⚠ Add validator on posts
└─ ⚠ Add validator on users
⚠ This migration contains destructive operations that may cause data loss.
ℹ Operation preview
db.runCommand({ collMod: "categories", validator: {"$jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"name":{"bsonType":"string"}},"required":["_id","name"]}}, validationLevel: "strict", validationAction: "error" })
db.runCommand({ collMod: "posts", validator: {"$jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"author":{"bsonType":"objectId"},"categories":{"bsonType":"array","items":{"bsonType":"objectId"}},"content":{"bsonType":"string"},"published":{"bsonType":"bool"},"title":{"bsonType":"string"}},"required":["_id","author","categories","content","published","title"]}}, validationLevel: "strict", validationAction: "error" })
db.runCommand({ collMod: "users", validator: {"$jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"email":{"bsonType":"string"},"name":{"bsonType":"string"},"profile":{"oneOf":[{"bsonType":"null"},{"additionalProperties":false,"bsonType":"object","properties":{"bio":{"bsonType":["null","string"]}}}]}},"required":["_id","email","name"]}}, validationLevel: "strict", validationAction: "error" })
ℹ This is a dry run. No changes were applied.Adding a validator to a populated collection counts as destructive, so db update asks you to type the database name before applying it. On MongoDB it currently cannot resolve that name and stops with CLI.CONSENT_TOKEN_UNRESOLVED. Apply the three previewed collMod commands yourself: paste them into mongosh against your database. Each returns { ok: 1 }.
Then record that the database matches the contract, and verify it:
✔ Planned 3 operation(s) across 1 contract space
App space
├─ ⚠ Add validator on categories
├─ ⚠ Add validator on posts
└─ ⚠ Add validator on users
⚠ This migration contains destructive operations that may cause data loss.
ℹ Operation preview
db.runCommand({ collMod: "categories", validator: {"$jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"name":{"bsonType":"string"}},"required":["_id","name"]}}, validationLevel: "strict", validationAction: "error" })
db.runCommand({ collMod: "posts", validator: {"$jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"author":{"bsonType":"objectId"},"categories":{"bsonType":"array","items":{"bsonType":"objectId"}},"content":{"bsonType":"string"},"published":{"bsonType":"bool"},"title":{"bsonType":"string"}},"required":["_id","author","categories","content","published","title"]}}, validationLevel: "strict", validationAction: "error" })
db.runCommand({ collMod: "users", validator: {"$jsonSchema":{"additionalProperties":false,"bsonType":"object","properties":{"_id":{"bsonType":"objectId"},"email":{"bsonType":"string"},"name":{"bsonType":"string"},"profile":{"oneOf":[{"bsonType":"null"},{"additionalProperties":false,"bsonType":"object","properties":{"bio":{"bsonType":["null","string"]}}}]}},"required":["_id","email","name"]}}, validationLevel: "strict", validationAction: "error" })
ℹ This is a dry run. No changes were applied.Adding a validator to a populated collection counts as destructive, so db update asks you to type the database name before applying it. On MongoDB it currently cannot resolve that name and stops with CLI.CONSENT_TOKEN_UNRESOLVED. Apply the three previewed collMod commands yourself: paste them into mongosh against your database. Each returns { ok: 1 }.
Then record that the database matches the contract, and verify it:
bunx prisma db sign
bunx prisma db verifypnpm prisma db sign
pnpm prisma db verifyyarn prisma db sign
yarn prisma db verifynpx prisma db sign
npx prisma db verify✔ Database signed
from: none
to: 7a7760fc1ac91a781b0f6509b8c495fa3f6ae2be1ba2815bceccbf75286b93c6✔ Database marker and schema match contractFrom here on, schema changes go through the contract: edit contract.prisma, run contract emit, then db update for a direct development update or migration plan followed by db migrate for a checked-in migration.
The validators are strict: a write that includes a field the contract does not declare now fails, which is what a leftover Mongoose model produces:
Document failed validation
{ operatorName: 'additionalProperties', specifiedAs: { additionalProperties: false }, additionalProperties: [ '__v' ] }✔ Database signed
from: none
to: 7a7760fc1ac91a781b0f6509b8c495fa3f6ae2be1ba2815bceccbf75286b93c6✔ Database marker and schema match contractFrom here on, schema changes go through the contract: edit contract.prisma, run contract emit, then db update for a direct development update or migration plan followed by db migrate for a checked-in migration.
The validators are strict: a write that includes a field the contract does not declare now fails, which is what a leftover Mongoose model produces:
Document failed validation
{ operatorName: 'additionalProperties', specifiedAs: { additionalProperties: false }, additionalProperties: [ '__v' ] }Run npx prisma@latest init once to install the Prisma ORM skills for your coding agent and keep them matching your installed packages. Prompts that map to this guide:
- "Using the prisma-8 skill, translate the Mongoose schemas in
src/modelsintosrc/prisma/contract.prismaand emit it." - "Replace the Mongoose queries in
src/controllers/posts.tswithdb.orm.postscalls; use a.lookuppipeline forpopulateon arrays." - "Add
GET /users/:id/poststhat returns a user's published posts with.whereand.include."
- Model documents, embedded types, and references for MongoDB.
- Read data and write data with the ORM API.
- Aggregate with the pipeline builder.
- Understand migrations now that Prisma ORM owns the schema.
- Coming from Prisma ORM 6 on MongoDB instead of Mongoose? See the MongoDB upgrade guide.