Add Prisma ORM and MongoDB to an existing app
Add Prisma ORM to an app you already have, create the collections in an empty MongoDB database, run a first query, and apply a first migration.
This page uses MongoDB. Use PostgreSQL instead.
Use this page when you already have an app and its database has no collections yet. By the end, your app has one model, the database has a collection for it, and a script writes and reads a document. You also change the model once and apply the change to the database with a migration. If your database already has collections, follow Add Prisma ORM to an existing MongoDB database instead.
Prisma ORM 8 is a release candidate. Prisma ORM 7 remains fully supported; its docs live at /orm/v7 and its setup paths at /v7/getting-started.
For what release candidate means, when the final release is expected, and how to stay on version 7, see Release status.
Prerequisites
- A project directory with a
package.json, on Node.js 22.18 or newer on the 22 line, 24.11 or newer on the 24 line, or 26 or newer (24 recommended). - A way to run a TypeScript file. This page uses
tsx. If your project does not have it, runnpm install --save-dev tsx typescript. - An empty MongoDB database, version 8.0 or newer. A single
mongodserver is enough. You need a replica set only for transactions and change streams.
1. Add Prisma ORM to the project
The orm init command below changes your tsconfig.json and your package.json. In tsconfig.json, it sets module to preserve and moduleResolution to bundler. If your package.json has no "type" field, it adds "type": "module". If it has "type": "commonjs", the command keeps it and prints a warning. If your app runs as CommonJS, for example because it loads files with require or because tsc compiles it to require calls, follow In a CommonJS project before you start your app again. tsx runs the scripts on this page in both kinds of app, so you can finish this page first.
From the root of your project, run:
bunx prisma@latest orm init --yes --target mongodb --authoring psl --write-envprisma@latest runs Prisma ORM 8, and --target mongodb picks MongoDB. --authoring psl picks PSL, the Prisma Schema Language, which is the .prisma file format you know from earlier Prisma ORM versions. --write-env writes a .env file, unless you already have one. --yes accepts the default answer to every question, so the command asks nothing.
The command installs the packages, writes the files, and ends with the summary below. In package.json, it adds the packages with their current version numbers, and a contract:emit script that runs prisma contract emit. If your project already has a tsconfig.json or a .gitignore, the command edits that file and keeps the rest of it.
│ 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
├─ .env
├─ tsconfig.json
├─ .gitignore
├─ .gitattributes
└─ package.json
installed
├─ @prisma/orm-mongo
├─ dotenv
├─ prisma@latest (dev)
├─ @types/node (dev)
└─ @prisma/cli-engine@0.7.0 (dev)
✔ Done. Open prisma-8.md to get started.You work with three of these files:
src/prisma/contract.prismaholds your models. It is whatschema.prismawas in earlier Prisma ORM versions, and Prisma ORM 8 calls it the contract. The summary lists it asschema.prisma.config.tstells theprismacommands where the contract is and which database to connect to.src/prisma/db.tsis the file your app imports to run queries.
You do not need prisma-8.md for this page. It is a short reference for writing queries. .gitattributes marks the files that Prisma ORM generates, so that GitHub collapses them in pull request diffs.
orm init wrote src/prisma/db.ts, and you do not need to change it:
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']!,
});The first line loads .env, so every file that imports db reads DATABASE_URL from there. The two contract files it imports are written by contract emit in step 4.
From now on, every prisma command ends with the line Prisma agent skills are out of date. You can ignore it, because it does not change what the command does. Agent skills are instruction files that AI coding tools read, and the line appears because your project has none yet. To add them and stop the line, run npx prisma@latest init. In Prisma ORM 8, init adds the skills and a postinstall script that keeps them up to date, and it leaves your contract and your .env alone. orm init is the command that sets up Prisma ORM.
2. Set the connection string
If you had no .env, orm init wrote one with a placeholder, DATABASE_URL="mongodb://user:password@localhost:27017/mydb". Open .env and set DATABASE_URL to the connection string of your database:
DATABASE_URL="mongodb://username:password@host:27017/database"The part after the last /, and before any ?, is the name of the database.
orm init added .env to .gitignore, so the password stays out of version control.
3. Write one model
orm init wrote a starter contract with a User and a Post model. Replace the contents of src/prisma/contract.prisma with one model:
// use prisma-8
model User {
id ObjectId @id @map("_id")
email String @unique
name String?
@@map("users")
}Keep the first line, because contract emit reads only .prisma files that start with it. @@map("users") names the collection that stores the model. MongoDB stores the identifier of every document in _id. On MongoDB, contract emit names each field in contract.json by the name in its @map, so in queries and in results you use _id, not id. The same goes for any other field with @map. The file has no datasource or generator block. The connection string comes from .env, and contract emit in the next step takes the place of the generator. Data modeling covers field types and relations.
4. Generate the files your code imports
Run contract emit after every change to the contract. It takes the place of prisma generate. From here on, commands start with npx prisma, without @latest. That runs the version of Prisma ORM that orm init installed in your project:
bunx 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.tsThe command writes contract.json and contract.d.ts next to the contract. Commit both files, because db.ts imports them.
5. Create the collection
bunx prisma db init✔ Introspecting database schema
✔ Planning migration
✔ Initialising database across spaces
│ contract: src/prisma/contract.json
│ database: mongodb://127.0.0.1:27029/app1
✔ Applied 2 operation(s) across 1 contract space
App space
├─ Create collection users
├─ Create index on users (email:1)
└─ marker 778f20d8e17f0107ce8a9f84577123728a700cb38e1b2b2689949ae658d3776e
✔ Advanced ref "db" → 778f20d8e17f0107ce8a9f84577123728a700cb38e1b2b2689949ae658d3776edb init creates the collections and indexes your contract declares. It also gives each collection a validator, which is the MongoDB rule that lists the fields a document may have. MongoDB then rejects a document with a field that the contract does not declare. The output names two more things it wrote:
- The
markeris a record in the database. It holds the hash of your contract, which is the long text in the output, so that later commands can tell which version of the contract the database has. - The
dbref is the filemigrations/app/refs/db.jsonin your project. It holds the same hash. The lineAdvanced ref "db"means that the command wrote this file.migration planin step 7 reads it to find out which contract your development database has.
You can ignore the word space in the output, because all your migrations are in migrations/app/.
Commit the migrations/ directory together with your code, because migration plan reads it on every later change. If the command fails with DRIVER.CONNECTION_FAILED, the value of DATABASE_URL in .env is wrong or the database is not reachable.
6. Write and read a document
Create script.ts in the root of your project:
import { db } from "./src/prisma/db";
async function main() {
const created = await db.orm.users.create({
email: "alice@example.com",
name: "Alice",
});
console.log("Created:", created);
const users = await db.orm.users.all();
console.log("All users:", users);
await db.close();
}
main().catch((error) => {
console.error(error);
process.exit(1);
});db.orm queries your models, the way Prisma Client did in earlier Prisma ORM versions. You reach this model as db.orm.users, because on MongoDB you address a model by the name of its collection, which is the name in @@map. await db.close() closes the database connections, and without it the script keeps running after the last query. create takes the fields directly, with no data wrapper, and .all() does what findMany() did.
Run it:
bunx tsx script.tsCreated: {
_id: '6abc2ce7b04f66be2c85564d',
email: 'alice@example.com',
name: 'Alice'
}
All users: [
{
_id: '6abc2ce7b04f66be2c85564d',
email: 'alice@example.com',
name: 'Alice'
}
]The script passes no identifier, and the new document gets one in _id, returned as a string. To find a document by it, filter on _id, as in db.orm.users.where({ _id: created._id }).first().
In your app, import db from src/prisma/db.ts the same way. Reading data and Writing data show the other queries.
7. Change the model and apply the change
Add a field to the model:
// use prisma-8
model User {
id ObjectId @id @map("_id")
email String @unique
name String?
phone String?
@@map("users")
}Generate the files again, then plan a migration. migration plan compares the new contract with the last one you applied to your development database, and writes the difference into a new directory under migrations/app/. --name sets the end of the directory's name, after the date and time.
bunx prisma contract emit
bunx prisma migration plan --name add_user_phone│ contract: src/prisma/contract.json
│ migrations: migrations/app
│ name: add_user_phone
✔ Planned baseline (2 operation(s)) + 1 operation(s)
migrations/app/20260929T2126_baseline
├─ Create collection users
└─ Create index on users (email:1)
migrations/app/20260929T2127_add_user_phone
└─ Update validator on users
from: 778f20d8e17f0107ce8a9f84577123728a700cb38e1b2b2689949ae658d3776e
to: f0035d1963f127dc8ee91e154d51810755ee91b090659127881d1ab31e03e3cf
baseline: migrations/app/20260929T2126_baseline
app space: migrations/app/20260929T2127_add_user_phoneThe output continues with a preview of the MongoDB commands. The first plan in a project writes two directories. The baseline holds the steps that create the collection and the index, which db init already ran on your development database. The second directory is your change. Your change updates the validator, so that a document may have the phone field.
Apply the migration:
bunx prisma db migrate --advance-ref db✔ Running migration plan across spaces
│ migrations: migrations
│ database: mongodb://127.0.0.1:27029/app1
✔ Applied 1 migration(s) (1 operation(s)) across 1 contract space(s)
App space
├─ Update validator on users
└─ marker f0035d1963f127dc8ee91e154d51810755ee91b090659127881d1ab31e03e3cf
✔ Advanced ref "db" → f0035d1963f127dc8ee91e154d51810755ee91b090659127881d1ab31e03e3cfdb migrate runs only the migrations that the database does not have yet, so here it runs only your change. --advance-ref db writes the new hash to the db ref, so that the next migration plan contains only your next change. Pass it every time you apply a migration to your development database. Commit the migrations/ directory again.
To apply the migrations to any other database, such as production, pass its connection string. Leave out --advance-ref, because the db ref describes your development database:
bunx prisma db migrate --db "$PRODUCTION_DATABASE_URL"db migrate runs the migrations that this database does not have yet. An empty database has no marker, so there db migrate runs the baseline and then your change.
Every later change follows the same four steps, which take the place of prisma migrate dev: edit src/prisma/contract.prisma, run npx prisma contract emit, run npx prisma migration plan --name <name>, and run npx prisma db migrate --advance-ref db.
Next steps
- Data modeling for relations, more field types, and indexes.
- Generating a migration for what a migration directory contains and how to edit it.
db updateto apply a contract change to a development database without writing migration files.
