# Add Prisma ORM to an existing PostgreSQL database (/docs/prisma-orm/add-to-existing-project/postgresql) > 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. Add Prisma ORM to an app whose PostgreSQL database already has tables. Location: Prisma ORM > Add To Existing Project > Add Prisma ORM to an existing PostgreSQL database This page uses PostgreSQL. [Use MongoDB instead](https://www.prisma.io/docs/prisma-orm/add-to-existing-project/mongodb). On this page you add Prisma ORM to an app whose PostgreSQL database already has tables. You run `orm init`, generate the contract from your tables, run two queries, and apply your first change to the tables. The contract is the file that holds your models. In Prisma ORM 7, it was `schema.prisma`. Your app must reach its PostgreSQL database and run on Node.js 22.18 or newer. Work against a development copy of your database, not production. [Step 9](#9-make-your-first-change-to-the-tables) shows how to apply your changes to production afterwards. If your database has no tables yet, follow [Add Prisma ORM and PostgreSQL to an existing app](https://www.prisma.io/docs/prisma-orm/quickstart/existing-app/postgresql) instead, which creates the tables for you. If you want Prisma ORM to create a new app for you, follow [Create a new app with PostgreSQL](https://www.prisma.io/docs/prisma-orm/quickstart/postgresql). If your app uses Prisma ORM 7 today, follow [Prisma ORM 7 to 8 (PostgreSQL)](https://www.prisma.io/docs/guides/upgrade-prisma-orm/postgresql). > [!NOTE] > Using Prisma ORM 7? > > Prisma ORM 8 is the current release, as a release candidate. Prisma ORM 7 remains fully supported; its docs live at [/orm/v7](https://www.prisma.io/docs/orm/v7) and its setup paths at [/v7/getting-started](https://www.prisma.io/docs/v7/getting-started). > > For what release candidate means, when the final release is expected, and how to stay on version 7, see [Release status](https://www.prisma.io/docs/orm/release-status). For the Prisma ORM 8 name of every Prisma ORM 7 API, see [Coming from Prisma ORM 7](https://www.prisma.io/docs/orm/coming-from-prisma-orm-7). ## 1. Install `tsx` [#1-install-tsx] The scripts on this page run with `tsx`. If your project does not have it, install it: #### bun ```bash bun add --dev tsx typescript ``` #### pnpm ```bash pnpm add --save-dev tsx typescript ``` #### yarn ```bash yarn add --dev tsx typescript ``` #### npm ```bash npm install --save-dev tsx typescript ``` ## 2. Initialize Prisma ORM [#2-initialize-prisma-orm] 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](https://www.prisma.io/docs/cli/orm-init#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: #### bun ```bash bunx prisma@latest orm init --yes --target postgres --authoring psl --write-env ``` #### pnpm ```bash pnpm dlx prisma@latest orm init --yes --target postgres --authoring psl --write-env ``` #### yarn ```bash yarn dlx prisma@latest orm init --yes --target postgres --authoring psl --write-env ``` #### npm ```bash npx prisma@latest orm init --yes --target postgres --authoring psl --write-env ``` `prisma@latest` runs Prisma ORM 8, and `--target postgres` picks PostgreSQL. `--authoring psl` picks PSL, the Prisma Schema Language, which is the `.prisma` file format you know from Prisma ORM 7. `--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 Prisma ORM packages and writes its files. You use three of them on this page: * `src/prisma/contract.prisma`, an example contract. Step 4 replaces it with a contract generated from your tables. * `src/prisma/db.ts`, the file your code imports to run queries. * `.env`, which holds the connection string. The command also writes `prisma-8.md`, a short reference for writing queries. You do not need it for this page. 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`](https://www.prisma.io/docs/cli/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. ## 3. Set your database connection string [#3-set-your-database-connection-string] If you had no `.env`, `orm init` wrote one with a placeholder, `DATABASE_URL="postgresql://user:password@localhost:5432/mydb"`. Set `DATABASE_URL` in `.env` to the connection string of the development copy of your database: ```text title=".env" DATABASE_URL="postgres://username:password@host:5432/database?sslmode=require" ``` `orm init` wrote `src/prisma/db.ts`, and you do not need to change it: ```typescript title="src/prisma/db.ts" import 'dotenv/config'; import postgres from '@prisma/orm-postgres/runtime'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; export const db = postgres({ 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 5. ## 4. Generate the contract from your tables [#4-generate-the-contract-from-your-tables] `contract infer` reads the tables in your database and writes a contract that describes them. It does what `prisma db pull` did in Prisma ORM 7. Run: #### 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 ✔ Connecting to database... ✔ Introspecting database schema... Overwriting existing file: src/prisma/contract.prisma │ database: postgres://****@127.0.0.1:54329/legacy ✔ Contract written to src/prisma/contract.prisma ``` The command replaces the example contract from step 2. For a database with a `user` table and a `post` table, the new contract looks like this: ```prisma title="src/prisma/contract.prisma" // use prisma-8 // Contract inferred from the live database schema. Edit as needed, then run `prisma contract emit`. namespace public { model User { id Int @id(map: "user_pkey") @default(autoincrement()) email String @unique(map: "user_email_key") name String? role String @default("member") age Int? createdAt Timestamptz @default(now()) @map("created_at") posts Post[] @@check(expression: "(age >= 0)", map: "user_age_check") @@map("user") } model Post { id Int @id(map: "post_pkey") @default(autoincrement()) title VarChar(200) published Boolean @default(false) authorId Int @map("author_id") author User @relation(fields: [authorId], references: [id], onDelete: Cascade, map: "post_author_id_fkey") @@index([authorId], map: "post_author_id_idx") @@index([title], map: "post_published_idx", where: "published") @@index(expression: "lower(title::text)", map: "post_title_lower_idx") @@rls @@map("post") } policy_select post_read { target = Post roles = [public] using = "published" @@map("post_read") } } ``` Keep the first line, because `contract emit` reads only `.prisma` files that start with it. `namespace public` holds the models of the tables in the PostgreSQL schema `public`. Where Prisma ORM 7 wrote `DateTime @db.Timestamptz`, the contract writes `Timestamptz`, and `String @db.VarChar(200)` becomes `VarChar(200)`. In the policy block, `roles = [public]` is the PostgreSQL role `PUBLIC`, not the schema. `contract infer` reads these parts of the database and writes each one into the contract: | In the database | In the contract above | | ---------------------------------------------------------------------------- | --------------------------------------------------------- | | Primary keys and unique constraints | `@id`, `@unique` | | Column defaults | `@default("member")`, `@default(now())` | | Foreign keys, with what happens on delete | `@relation(..., onDelete: Cascade)` and the `posts` field | | Check constraints | `@@check` | | Indexes, including an index on part of a table and an index on an expression | the three `@@index` lines | | Row-level security and its policies | `@@rls` and the `policy_select` block | Review the file before you go on. You can rename models, and you can remove the models of tables you do not want to use yet. A model uses the table with exactly the model's name, unless the model has `@@map`. So for the table `user`, `contract infer` wrote the model `User` with `@@map("user")`. Keep the `@@map` lines when you rename a model, so that the model still reads the same table. A table without a model stays in your database, and later migrations leave it alone. Do not remove the model of a table that has row-level security policies, though. If you do, `db sign` in step 6 fails, because it finds policies that the contract does not declare. ### Date and time columns on Node.js 24 and older [#date-and-time-columns-on-nodejs-24-and-older] `contract infer` gives a `timestamptz` column the type `Timestamptz`, and a `timestamp` column the type `Timestamp`. Prisma ORM returns the values of these columns as `Temporal` objects. `Temporal` is the new date and time API of JavaScript. Node.js 26 has it built in. Node.js 24 and older do not, and there the first query that reads such a column fails with `RUNTIME.TEMPORAL_UNAVAILABLE`. On Node.js 24 or older, install the `temporal-polyfill` package: #### bun ```bash bun add temporal-polyfill ``` #### pnpm ```bash pnpm add temporal-polyfill ``` #### yarn ```bash yarn add temporal-polyfill ``` #### npm ```bash npm install temporal-polyfill ``` Then add this line at the top of `src/prisma/db.ts`: ```typescript title="src/prisma/db.ts" import "temporal-polyfill/global"; ``` If you would rather not add the package, change the type of each such field in the contract: `Timestamptz` becomes `TimestamptzString`, and `Timestamp` becomes `TimestampString`. The field then holds the text that PostgreSQL returns, such as `2026-09-30 07:36:38.60182+02`, and needs no `Temporal`. The column in the database stays as it is. ### Tables in other PostgreSQL schemas [#tables-in-other-postgresql-schemas] `contract infer` reads only the PostgreSQL schema named `public`. It skips tables in any other schema and prints no message about them. To use a table from another schema, write its model by hand inside a `namespace` block with the name of that schema. For a table `event` in the schema `audit`, add this to the end of the contract: ```prisma title="src/prisma/contract.prisma" namespace audit { model Event { id Int @id @default(autoincrement()) message String @@map("event") } } ``` `db sign` in step 6 checks this table like the tables in `public`. Your code reaches the model as `db.orm.audit.Event`. ## 5. Generate the files your code imports [#5-generate-the-files-your-code-imports] `contract emit` takes the place of `prisma generate`, and you run it after every change to the contract: #### bun ```bash bunx prisma contract emit ``` #### pnpm ```bash pnpm prisma contract emit ``` #### yarn ```bash yarn prisma contract emit ``` #### npm ```bash npx prisma contract emit ``` The command writes `src/prisma/contract.json` and `src/prisma/contract.d.ts`, and `db.ts` imports both of them. Commit them, because your app cannot run without them. If the contract has a policy, a check, or an index that contains SQL text, `contract emit` prints one warning for each, and still writes both files. Each warning starts like this: ```text no-copy (node:15169) [PN_EXACT_NAME_BODY_COMPARISON] Warning: check "user_age_check" uses map: with a SQL body. ``` You can ignore these warnings on a contract that `contract infer` wrote. They are about SQL text that you write by hand, which Prisma ORM compares character by character with the text that PostgreSQL reports. ## 6. Sign the database [#6-sign-the-database] `db sign` checks that your database has what the contract describes. Then it records which contract the database matches. `migration plan` in step 9 needs that record to start from the tables you already have, instead of from an empty database. #### bun ```bash bunx prisma db sign ``` #### pnpm ```bash pnpm prisma db sign ``` #### yarn ```bash yarn prisma db sign ``` #### npm ```bash npx prisma db sign ``` ```text no-copy ✔ Connecting to database... ✔ Verifying database schema... ✔ Signing database... │ contract: src/prisma/contract.json │ database: postgres://****@127.0.0.1:54329/legacy ✔ Database signed from: none to: 596586f61799d5b2585df874a00f8d6f12d73a522d2eefbcbec62dba4e9904a4 ✔ Advanced ref "db" → 596586f61799d5b2585df874a00f8d6f12d73a522d2eefbcbec62dba4e9904a4 ``` A table or a column that the database has and the contract does not declare is not a problem. When something the contract declares is missing from the database, `db sign` writes nothing and exits with code 4. For example, with a `phone` field in the `User` model and no `phone` column in the table: ```text no-copy ✘ Schema issues └─ ✘ missing: database/public/user/column:phone ✘ [CONTRACT.SCHEMA_VERIFICATION_FAILED] Database schema does not satisfy contract (1 failure) why: The live schema differs: missing: database/public/user/column:phone. ``` To fix it, remove from the contract what the database does not have, run `npx prisma contract emit`, and sign again. When the check passes, `db sign` stores a record in the database and writes files into your project: * A record in the database of which contract it matches, in the table `prisma_contract.marker`. The record holds the hash of the contract, which is the long text after `to:` in the output. * A copy of the contract, under `migrations/snapshots/` in your project. * The `db` ref, which is the file `migrations/app/refs/db.json`. It holds the same hash. The line `Advanced ref "db"` in the output means that the command wrote this file. The next [`migration plan`](https://www.prisma.io/docs/cli/migration-plan) reads the copy and the `db` ref, so it starts from the tables you already have. Commit the `migrations/` directory. Until you plan your first migration, you can add models for tables that are already in the database. Run `npx prisma contract emit` and then `npx prisma db sign` again, which checks the database against the new contract and replaces the record. ## 7. Query a model with `db.orm` [#7-query-a-model-with-dborm] `db.orm` queries your models, the way Prisma Client did in Prisma ORM 7. Create `script.ts` with the code below, which reads the `User` model from step 4. Use one of your own models and its fields in its place: ```typescript title="script.ts" import { db } from "./src/prisma/db"; async function main() { const users = await db.orm.public.User .select("id", "email", "name") .limit(2) .all(); console.log(users); await db.close(); } main().catch((error) => { console.error(error); process.exit(1); }); ``` Run it: #### bun ```bash bunx tsx script.ts ``` #### pnpm ```bash pnpm dlx tsx script.ts ``` #### yarn ```bash yarn dlx tsx script.ts ``` #### npm ```bash npx tsx script.ts ``` ```text no-copy [ { id: 1, email: 'alice@example.com', name: 'Alice' }, { id: 2, email: 'bob@example.com', name: 'Bob' } ] ``` ## 8. Query a table with `db.sql` [#8-query-a-table-with-dbsql] `db.sql` builds SQL queries: where `db.orm` names models and fields, `db.sql` names tables and columns as they are in the database. This example reads the table `user`, which the `User` model maps with `@@map("user")`. `.build()` returns the query without running it, and `db.runtime().query()` runs it. Replace `script.ts` with this version: ```typescript title="script.ts" import { db } from "./src/prisma/db"; async function main() { const query = db.sql.public.user .select("id", "email", "name") .limit(2) .build(); const rows = await db.runtime().query(query); console.log(rows); await db.close(); } main().catch((error) => { console.error(error); process.exit(1); }); ``` Run it again: #### bun ```bash bunx tsx script.ts ``` #### pnpm ```bash pnpm dlx tsx script.ts ``` #### yarn ```bash yarn dlx tsx script.ts ``` #### npm ```bash npx tsx script.ts ``` ```text no-copy [ { id: 1, email: 'alice@example.com', name: 'Alice' }, { id: 2, email: 'bob@example.com', name: 'Bob' } ] ``` ## 9. Make your first change to the tables [#9-make-your-first-change-to-the-tables] From here on, you change the tables by changing the contract. Add a field to a model in `src/prisma/contract.prisma`. This example adds `phone` to the `User` model from step 4, so use a model and a field of your own: ```prisma title="src/prisma/contract.prisma" model User { id Int @id(map: "user_pkey") @default(autoincrement()) email String @unique(map: "user_email_key") name String? phone String? // [!code ++] ``` Generate the files again, then plan a migration. `migration plan` compares the new contract with the last one you signed or applied, 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. #### bun ```bash bunx prisma contract emit bunx prisma migration plan --name add_user_phone ``` #### pnpm ```bash pnpm prisma contract emit pnpm prisma migration plan --name add_user_phone ``` #### yarn ```bash yarn prisma contract emit yarn prisma migration plan --name add_user_phone ``` #### npm ```bash npx prisma contract emit npx prisma migration plan --name add_user_phone ``` ```text no-copy │ contract: src/prisma/contract.json │ migrations: migrations/app │ name: add_user_phone ✔ Planned baseline (10 operation(s)) + 1 operation(s) migrations/app/20260929T2130_baseline ├─ Create schema "public" ├─ Create table "post" ├─ Create table "user" ├─ Add unique constraint on "user" (email) ├─ Create index "post_author_id_idx" on "post" ├─ Create index "post_published_idx" on "post" ├─ Create index "post_title_lower_idx" on "post" ├─ Add foreign key "post_author_id_fkey" on "post" ├─ Enable row-level security on "post" └─ Create RLS policy "post_read" on "post" migrations/app/20260929T2131_add_user_phone └─ Add column "phone" to "user" from: 596586f61799d5b2585df874a00f8d6f12d73a522d2eefbcbec62dba4e9904a4 to: 4ef74b97c298402786f23bf299e17baba815e8d521bc90aaab59ab7de747c969 baseline: migrations/app/20260929T2130_baseline app space: migrations/app/20260929T2131_add_user_phone ``` The output continues with a preview of the SQL. The first plan writes two directories. The baseline describes the tables you had when you ran `db sign`. It lists `Create table` operations, but `db migrate` does not run them on a database that already has those tables. The second directory is your change. Commit both directories. [The automatic baseline](https://www.prisma.io/docs/cli/migration-plan#the-automatic-baseline) explains when `migration plan` writes a baseline. You can ignore the word `space` in the output, because all your migrations are in `migrations/app/`. Apply the migration: #### bun ```bash bunx prisma db migrate --advance-ref db ``` #### pnpm ```bash pnpm prisma db migrate --advance-ref db ``` #### yarn ```bash yarn prisma db migrate --advance-ref db ``` #### npm ```bash npx prisma db migrate --advance-ref db ``` ```text no-copy ✔ Running migration plan across spaces │ migrations: migrations │ database: postgres://****@127.0.0.1:54329/legacy ✔ Applied 1 migration(s) (1 operation(s)) across 1 contract space(s) App space ├─ Add column "phone" to "user" └─ marker 4ef74b97c298402786f23bf299e17baba815e8d521bc90aaab59ab7de747c969 ✔ Advanced ref "db" → 4ef74b97c298402786f23bf299e17baba815e8d521bc90aaab59ab7de747c969 ``` `db migrate` runs only the migrations that the database does not have yet. Here it adds the `phone` column, and the rows in your tables stay as they are. `marker` in the output is the record from `db sign`, which now holds the new hash. Pass `--advance-ref db` every time you apply a migration to your development database, so that the next `migration plan` contains only your next change. To apply the migration to another database with the same tables, such as production, pass its connection string. Leave out `--advance-ref` there, because the `db` ref tracks your development database: #### bun ```bash bunx prisma db migrate --db "$PRODUCTION_DATABASE_URL" ``` #### pnpm ```bash pnpm prisma db migrate --db "$PRODUCTION_DATABASE_URL" ``` #### yarn ```bash yarn prisma db migrate --db "$PRODUCTION_DATABASE_URL" ``` #### npm ```bash npx prisma db migrate --db "$PRODUCTION_DATABASE_URL" ``` You do not need to run `db sign` on that database. `db migrate` finds that the tables of the baseline are already there, leaves them and their rows as they are, and then adds the `phone` column. ## Next steps [#next-steps] Every later change follows the same four steps: edit `src/prisma/contract.prisma`, run `npx prisma contract emit`, run `npx prisma migration plan --name `, and run `npx prisma db migrate --advance-ref db`. * [Generating a migration](https://www.prisma.io/docs/orm/migrations/generating-a-migration) for what a migration directory contains and how to edit it. * [`db update`](https://www.prisma.io/docs/cli/db-update) to apply a contract change to a development database without writing migration files. * [Coming from Prisma ORM 7](https://www.prisma.io/docs/orm/coming-from-prisma-orm-7) for the Prisma ORM 8 name of each Prisma ORM 7 call. ## Related pages - [`Add Prisma ORM to an existing MongoDB database`](https://www.prisma.io/docs/prisma-orm/add-to-existing-project/mongodb): Add Prisma ORM to an app whose MongoDB database already has collections.