# Mongoose (/docs/guides/switch-to-prisma-orm/from-mongoose) > For the complete Prisma documentation index, see [llms.txt](https://www.prisma.io/docs/llms.txt). A markdown version of any docs page is available by appending `.md` to its URL. Migrate an existing Mongoose app to Prisma ORM step by step, against the same MongoDB database, without moving any data. Location: Guides > Switch to Prisma ORM > Mongoose ## Introduction [#introduction] 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. > [!NOTE] > Using Prisma ORM 7? > > Prisma ORM 8 is the current release. Prisma ORM 7 remains fully supported but has no MongoDB connector; the Prisma ORM 7 version of this guide, which uses Prisma ORM 6.19 for MongoDB, is at [/guides/v7/switch-to-prisma-orm/from-mongoose](https://www.prisma.io/docs/guides/v7/switch-to-prisma-orm/from-mongoose). ## Prerequisites [#prerequisites] * A Mongoose project you want to migrate (this guide uses TypeScript and Express) * [Node.js](https://nodejs.org) 24 or later * A MongoDB replica set. MongoDB Atlas is one already; locally, start `mongod` with `--replSet rs0` and run `rs.initiate()` once * Basic familiarity with Mongoose ## Use with your agent [#use-with-your-agent] To delegate this guide to your coding agent, copy the prompt below and hand it over: ```text 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("")`, 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.` 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 them with `npx prisma db update --no-interactive --confirm `, then run `npx prisma db verify`. ``` ## 1. Prepare for migration [#1-prepare-for-migration] ### 1.1. Understand the migration process [#11-understand-the-migration-process] 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: 1. Add Prisma ORM to the project with `orm init` 2. Describe the collections Mongoose manages in a contract and emit it 3. Replace Mongoose queries with Prisma ORM queries, one route at a time 4. 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. ### 1.2. The example app [#12-the-example-app] The app has three Mongoose models. `User` has a nested `profile`, `Post` references a `User` and an array of `Category` documents: ```typescript title="src/models/user.ts" 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); ``` ```typescript title="src/models/post.ts" 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`: ```typescript title="src/server.ts" 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: ```json no-copy [{"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. ### 1.3. Add Prisma ORM to the project [#13-add-prisma-orm-to-the-project] Run `orm init` in the project root. It adds Prisma ORM to the existing app; it does not scaffold a new one: #### bun ```bash bunx prisma@latest orm init --target mongodb ``` #### pnpm ```bash pnpm dlx prisma@latest orm init --target mongodb ``` #### yarn ```bash yarn dlx prisma@latest orm init --target mongodb ``` #### npm ```bash npx prisma@latest orm init --target mongodb ``` 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: ```text no-copy ✔ npm add @prisma/orm-mongo dotenv ✔ npm add -D prisma@latest ✔ npm add -D @prisma/cli-engine@0.6.3 ✔ 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.3 (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](https://www.prisma.io/docs/cli/orm-init#in-a-commonjs-project) before you start the server again. Install the [Prisma agent skills](https://www.prisma.io/docs/cli/skills) for your coding agent with [`init`](https://www.prisma.io/docs/cli/init), which also adds a `postinstall` hook that resyncs them on every install: #### bun ```bash bunx --bun prisma@latest init ``` #### pnpm ```bash pnpm dlx prisma@latest init ``` #### yarn ```bash yarn dlx prisma@latest init ``` #### npm ```bash npx prisma@latest init ``` If a later CLI command reports that the skills are out of date, run `npx prisma skills sync`. ### 1.4. Configure the connection [#14-configure-the-connection] `orm init` wrote a `prisma.config.ts` that loads `.env` and points at the contract: ```typescript title="prisma.config.ts" 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: ```typescript title="src/prisma/db.ts" 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({ 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`: ```bash title=".env" DATABASE_URL="mongodb://localhost:27017/blog?replicaSet=rs0&directConnection=true" ``` ## 2. Describe your collections in a contract [#2-describe-your-collections-in-a-contract] ### 2.1. Write the contract by hand [#21-write-the-contract-by-hand] On PostgreSQL, `contract infer` reads the live schema and writes a starter contract. MongoDB has no schema to read, so the command stops: #### bun ```bash bunx prisma contract infer --output ./src/prisma/contract.prisma ``` #### pnpm ```bash pnpm prisma contract infer --output ./src/prisma/contract.prisma ``` #### yarn ```bash yarn prisma contract infer --output ./src/prisma/contract.prisma ``` #### npm ```bash npx prisma contract infer --output ./src/prisma/contract.prisma ``` ```text no-copy ✘ [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: ```prisma title="src/prisma/contract.prisma" // 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 as `author`, the key Mongoose stores, and `.include("author")` fills it with the user document the way `populate("author")` does. `db.orm.posts.where({ authorId })` is a type error. * Mongoose adds a `__v` version 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. ### 2.2. Emit the contract [#22-emit-the-contract] Turn the contract into the artifacts the runtime and the CLI read: #### bun ```bash bunx prisma contract emit ``` #### pnpm ```bash pnpm prisma contract emit ``` #### yarn ```bash yarn prisma contract emit ``` #### npm ```bash npx prisma contract emit ``` ```text no-copy ✔ Resolving contract source... ✔ Emitting contract... │ contract: src/prisma/contract.json │ types: src/prisma/contract.d.ts ✔ Emitted contract.json and contract.d.ts ``` Run this again whenever you edit `contract.prisma`. Nothing else needs regenerating. ## 3. Replace Mongoose queries with Prisma ORM [#3-replace-mongoose-queries-with-prisma-orm] ### 3.1. Query equivalents [#31-query-equivalents] 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`: #### Mongoose ```typescript // 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"); ``` #### Prisma ORM ```typescript // 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](https://www.prisma.io/docs/orm/fundamentals/advanced-queries#mongodb-pipeline-builder). ### 3.2. Populate an array of references [#32-populate-an-array-of-references] `.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: ```typescript 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. ### 3.3. Update the routes [#33-update-the-routes] Replace the Mongoose imports and the `mongoose.connect` call with the `db` client, then rewrite each handler. The finished server: ```typescript title="src/server.ts" 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: ```bash curl http://localhost:3000/users ``` ```json no-copy [{"profile":{"bio":"Writes about databases"},"__v":0,"_id":"6aa2cd2ed97f8d7357dc9bd1","email":"alice@prisma.io","name":"Alice"},{"profile":null,"__v":0,"_id":"6aa2cd2ed97f8d7357dc9bd2","email":"bob@prisma.io","name":"Bob"}] ``` The same documents Mongoose returned, `__v` included, because both read the same collection. Bob's document has no `profile`, and because `profile` is optional in the contract, Prisma ORM returns it as `null`. ```bash curl http://localhost:3000/posts ``` ```json no-copy [{"_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}] ``` ```bash curl -X POST http://localhost:3000/users \ -H "content-type: application/json" \ -d '{"email":"carol@prisma.io","name":"Carol"}' ``` ```json no-copy {"_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. ### 3.4. Run both side by side [#34-run-both-side-by-side] 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: ```json no-copy [{"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. ## 4. Finish the migration [#4-finish-the-migration] ### 4.1. Remove Mongoose [#41-remove-mongoose] 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 ```bash bun remove mongoose bun add mongodb ``` #### pnpm ```bash pnpm remove mongoose pnpm add mongodb ``` #### yarn ```bash yarn remove mongoose yarn add mongodb ``` #### npm ```bash npm uninstall mongoose npm install mongodb ``` Delete the `models/` directory and any `mongoose.connect` call that is left. ### 4.2. Remove the version key [#42-remove-the-version-key] 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: ```javascript for (const c of ["users", "posts", "categories"]) { const r = db[c].updateMany({}, { $unset: { __v: "" } }); print(c, r.modifiedCount); } ``` ```text no-copy users 2 posts 3 categories 2 ``` If 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. ### 4.3. Let Prisma ORM own the schema [#43-let-prisma-orm-own-the-schema] Prisma ORM tracks a database by a signature and, on MongoDB, by a `$jsonSchema` validator per collection. Preview what it wants to apply: #### bun ```bash bunx prisma db update --dry-run ``` #### pnpm ```bash pnpm prisma db update --dry-run ``` #### yarn ```bash yarn prisma db update --dry-run ``` #### npm ```bash npx prisma db update --dry-run ``` ```text no-copy ✔ 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. The name is the one in `DATABASE_URL`, here `blog`. Apply the validators: #### bun ```bash bunx prisma db update ``` #### pnpm ```bash pnpm prisma db update ``` #### yarn ```bash yarn prisma db update ``` #### npm ```bash npx prisma db update ``` Type `blog` when the command asks. In CI, or with `--no-interactive`, where the command cannot ask, pass the name as `--confirm blog` instead. `db update` applies the three validators and records in the database that it matches the contract. Then verify it: #### bun ```bash bunx prisma db verify ``` #### pnpm ```bash pnpm prisma db verify ``` #### yarn ```bash yarn prisma db verify ``` #### npm ```bash npx prisma db verify ``` ```text no-copy ✔ Database marker and schema match contract ``` From 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`](https://www.prisma.io/docs/cli/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: ```text no-copy Document failed validation { operatorName: 'additionalProperties', specifiedAs: { additionalProperties: false }, additionalProperties: [ '__v' ] } ``` ## Common gotchas [#common-gotchas] > [!WARNING] > Field names are storage names > > The query API uses the field names stored in MongoDB, not the names on the left of `@map`. With `authorId ObjectId @map("author")`, filter with `.where({ author: id })` and read `post.author`. TypeScript rejects the contract-side name. > [!WARNING] > ObjectId filters in the pipeline builder > > In the pipeline builder, `.match((f) => f._id.eq(id))` sends the id as a plain string and matches nothing. Filter ObjectId fields through the ORM, `db.orm.posts.where({ _id: id })` or `db.orm.posts.where(MongoFieldFilter.eq("_id", id))`, which converts the id for you. In a pipeline, pass an `ObjectId` instead, as the [`match()` reference](https://www.prisma.io/docs/orm/reference/pipeline-builder#match) shows. > [!WARNING] > No transactions on MongoDB yet > > The MongoDB client has no `db.transaction(...)`. For multi-document atomicity, pass your own `MongoClient` to `mongo({ contractJson, mongoClient, dbName })` and use the driver's `session.withTransaction(...)`, as shown in the [transactions](https://www.prisma.io/docs/orm/fundamentals/transactions) docs. ## Prompt your coding agent [#prompt-your-coding-agent] Run [`npx prisma@latest init`](https://www.prisma.io/docs/cli/init) once to install the [Prisma ORM skills](https://www.prisma.io/docs/ai/tools/skills#available-skills-for-prisma-8) 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/models` into `src/prisma/contract.prisma` and emit it." * "Replace the Mongoose queries in `src/controllers/posts.ts` with `db.orm.posts` calls; use a `.lookup` pipeline for `populate` on arrays." * "Add `GET /users/:id/posts` that returns a user's published posts with `.where` and `.include`." ## Next steps [#next-steps] * [Model documents, embedded types, and references](https://www.prisma.io/docs/orm/data-modeling/mongodb) for MongoDB. * [Read data](https://www.prisma.io/docs/orm/fundamentals/reading-data) and [write data](https://www.prisma.io/docs/orm/fundamentals/writing-data) with the ORM API. * [Aggregate with the pipeline builder](https://www.prisma.io/docs/orm/fundamentals/advanced-queries#mongodb-pipeline-builder). * [Understand migrations](https://www.prisma.io/docs/orm/migrations/how-migrations-work) now that Prisma ORM owns the schema. * Coming from Prisma ORM 6 on MongoDB instead of Mongoose? See the [MongoDB upgrade guide](https://www.prisma.io/docs/guides/upgrade-prisma-orm/mongodb). ## Related pages - [`Drizzle`](https://www.prisma.io/docs/guides/switch-to-prisma-orm/from-drizzle): Switch an existing Drizzle app to Prisma ORM: infer a contract from your database, sign it, and replace Drizzle queries route by route. - [`SQL ORMs`](https://www.prisma.io/docs/guides/switch-to-prisma-orm/from-sql-orms): Learn how to migrate from Sequelize or TypeORM to Prisma ORM