# 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("<collection>")`, a `type` block per nested schema, `ObjectId` plus `@relation` for each `ref`, and `ObjectId[]` for arrays of refs. Then run `npx prisma@latest 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@latest db update --dry-run`, apply the previewed collMod statements, then run `npx prisma@latest db sign` and `npx prisma@latest 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`: the contract is emitted once to `contract.json` and `contract.d.ts`, and the runtime reads them.

### 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 around. 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.3.0
✔ 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.3.0 (dev)
✔ Done. Open prisma-8.md to get started.
```

`@prisma/orm-mongo` is the MongoDB runtime. It uses the `mongodb` driver, the same one Mongoose already depends on, so nothing else is installed. If your `package.json` declares `"type": "commonjs"`, `orm init` leaves it alone and prints a warning; the generated files are ES modules, so set `"type": "module"` if you can.

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@latest 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<Contract>({
  contractJson,
  url: process.env['DATABASE_URL']!,
});
```

Both read `DATABASE_URL`. 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@latest contract infer --output ./src/prisma/contract.prisma
```

#### pnpm

```bash
pnpm dlx prisma@latest contract infer --output ./src/prisma/contract.prisma
```

#### yarn

```bash
yarn dlx prisma@latest contract infer --output ./src/prisma/contract.prisma
```

#### npm

```bash
npx prisma@latest 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   Boolean
  author      User       @relation(fields: [authorId], references: [id])
  authorId    ObjectId   @map("author")
  categoryIds ObjectId[] @map("categories")
  @@map("posts")
}
```

Two things to know before you go on:

* 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@latest contract emit
```

#### pnpm

```bash
pnpm dlx prisma@latest contract emit
```

#### yarn

```bash
yarn dlx prisma@latest contract emit
```

#### npm

```bash
npx prisma@latest 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. `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);
```

Read plans run with `runtime.query(plan)`. `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"},{"__v":0,"_id":"6aa2cd2ed97f8d7357dc9bd2","email":"bob@prisma.io","name":"Bob"}]
```

The same documents Mongoose returned, `__v` included, because both read the same collection.

```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@latest db update --dry-run
```

#### pnpm

```bash
pnpm dlx prisma@latest db update --dry-run
```

#### yarn

```bash
yarn dlx prisma@latest db update --dry-run
```

#### npm

```bash
npx prisma@latest 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. 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:

  

#### bun

```bash
bunx prisma@latest db sign
bunx prisma@latest db verify
```

#### pnpm

```bash
pnpm dlx prisma@latest db sign
pnpm dlx prisma@latest db verify
```

#### yarn

```bash
yarn dlx prisma@latest db sign
yarn dlx prisma@latest db verify
```

#### npm

```bash
npx prisma@latest db sign
npx prisma@latest db verify
```

```text no-copy
✔ Database signed

from:  none
to:    7a7760fc1ac91a781b0f6509b8c495fa3f6ae2be1ba2815bceccbf75286b93c6
```

```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 carries 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]
> Writing arrays of ObjectIds
> 
> Passing a whole `ObjectId[]` value, such as `categories: [id]`, to `.create(...)` or `.update({...})` fails with `Failed to encode parameter mongo/objectId@1`. Create the document without the array and add ids with a field operation: `db.orm.posts.where({ _id }).update((p) => [p.categories.push(id)])`.

> [!WARNING]
> ObjectId filters in the pipeline builder
> 
> `.match((f) => f._id.eq(id))` and `MongoFieldFilter.eq("_id", id)` send the id as a plain string and match nothing. Filter ObjectId fields through the ORM, `db.orm.posts.where({ _id: id })`, which encodes them for you.

> [!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