# Multiple databases (/docs/guides/database/multiple-databases)

> 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.

Connect one Next.js app to two PostgreSQL databases with Prisma ORM: one contract, config, and client per database, selected with the --config flag.

Location: Guides > Database > Multiple databases

## Introduction [#introduction]

This guide shows you how to use two databases from one [Next.js](https://nextjs.org/) app with Prisma ORM. You give each database its own data contract, its own `prisma.config.ts`, and its own Prisma ORM client, then render data from both on one page. The same layout works for any number of databases and is a good fit for multi-tenant apps or for keeping unrelated data in separate databases.

Every command and code block below was run against two local PostgreSQL databases, `users` and `posts`.

> [!NOTE]
> Using Prisma ORM 7?
> 
> Prisma ORM 8 is the current release. Prisma ORM 7 remains fully supported; the Prisma ORM 7 version of this guide is at [/guides/v7/database/multiple-databases](https://www.prisma.io/docs/guides/v7/database/multiple-databases).

## Prerequisites [#prerequisites]

* [Node.js](https://nodejs.org) 24 or later
* Two PostgreSQL connection strings, or nothing at all: `npx create-db@latest` can create a [Prisma Postgres](https://www.prisma.io/docs/postgres) database for you, run it twice for two
* A [Vercel](https://vercel.com/) account if you want to deploy at the end

## 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
Create a Next.js app that reads from two PostgreSQL databases with Prisma ORM, following https://www.prisma.io/docs/guides/database/multiple-databases.md.

1. Scaffold with `npx create-next-app@latest my-multi-db-app --yes`, then in the project run `npx prisma@latest orm init --yes --target postgres --authoring psl --schema-path ./prisma/users/contract.prisma`. Run `npx prisma@latest init` so the Prisma agent skills are installed, and use them.
2. Rename `prisma.config.ts` to `prisma.users.config.ts`, point it at `USERS_DATABASE_URL` and `./prisma/users/migrations`, and reduce `prisma/users/contract.prisma` to a `User` model. Create the same three files for the posts database: `prisma.posts.config.ts` reading `POSTS_DATABASE_URL`, `prisma/posts/contract.prisma` with a `Post` model that stores `authorId Int` (no relation across databases), and `prisma/posts/db.ts`. Export `usersDb` and `postsDb` from the two `db.ts` files.
3. Put both connection strings in `.env` (use the ones I give you, or create two Prisma Postgres databases with `npx create-db@latest` and show me the claim URLs). Add package scripts that run `prisma contract emit`, `prisma db init`, and `prisma db verify` once per config with `--config`, then run them.
4. Write a seed script that inserts two users and two posts, run it with `node prisma/seed.ts`, and rewrite `app/page.tsx` as a server component that lists users from `usersDb` and posts from `postsDb`, joining authors in application code.
5. Start `npm run dev` in the background, verify http://localhost:3000 renders both lists, stop the server, and confirm `npm run build` passes.
```

## 1. Set up a Next.js project [#1-set-up-a-nextjs-project]

Create a new Next.js app and accept the defaults (TypeScript, Tailwind CSS, App Router, no `src` directory):

  

#### bun

```bash
bunx create-next-app@latest my-multi-db-app
cd my-multi-db-app
```

#### pnpm

```bash
pnpm dlx create-next-app@latest my-multi-db-app
cd my-multi-db-app
```

#### yarn

```bash
yarn dlx create-next-app@latest my-multi-db-app
cd my-multi-db-app
```

#### npm

```bash
npx create-next-app@latest my-multi-db-app
cd my-multi-db-app
```

This guide starts from `create-next-app` and adds Prisma ORM with `orm init` instead of using the `next` template of `create-prisma`. That template wires a single database and declares it for Prisma Compute; here you want exactly the Prisma files, once per database, and nothing else.

## 2. Add Prisma ORM for the users database [#2-add-prisma-orm-for-the-users-database]

Run `orm init` from the project root. The `--schema-path` flag puts the contract in a folder named after the database, and `orm init` places the client file next to it:

  

#### bun

```bash
bunx prisma@latest orm init --target postgres --authoring psl --schema-path ./prisma/users/contract.prisma
```

#### pnpm

```bash
pnpm dlx prisma@latest orm init --target postgres --authoring psl --schema-path ./prisma/users/contract.prisma
```

#### yarn

```bash
yarn dlx prisma@latest orm init --target postgres --authoring psl --schema-path ./prisma/users/contract.prisma
```

#### npm

```bash
npx prisma@latest orm init --target postgres --authoring psl --schema-path ./prisma/users/contract.prisma
```

The command installs `@prisma/orm-postgres` and `dotenv`, adds `prisma` as a dev dependency, sets `"type": "module"` in `package.json`, updates `tsconfig.json`, and writes these files:

```text no-copy
prisma/users/contract.prisma
prisma/users/db.ts
prisma.config.ts
prisma-8.md
.env.example
```

There is no `prisma generate` and no generated client package. The emitted `contract.json` and `contract.d.ts` next to the contract are what the runtime and the type checker read.

### 2.1. Give the config a database-specific name [#21-give-the-config-a-database-specific-name]

Prisma ORM commands read `./prisma.config.ts` by default and take a different file with the global [`--config`](https://www.prisma.io/docs/cli/global-flags) flag. Rename the generated config so each database has one:

```bash
mv prisma.config.ts prisma.users.config.ts
```

Point it at the users contract, a users-only migrations folder, and a `USERS_DATABASE_URL` variable:

```typescript title="prisma.users.config.ts"
import "dotenv/config";
import { definePrismaConfig } from "prisma/config";
import { defineConfig as ormConfig } from "@prisma/orm-postgres/config";

export default definePrismaConfig({
  orm: ormConfig({
    contract: "./prisma/users/contract.prisma",
    migrations: {
      dir: "./prisma/users/migrations", // [!code ++]
    },
    db: {
      connection: process.env.USERS_DATABASE_URL!, // [!code ++]
    },
  }),
});
```

`migrations.dir` matters once you use checked-in migrations: without it both configs would write to the same `./migrations` folder.

### 2.2. Define the users contract [#22-define-the-users-contract]

Replace the starter contract with a single `User` model:

```prisma title="prisma/users/contract.prisma"
// use prisma-8

model User {
  id        Int               @id @default(autoincrement())
  email     String            @unique
  name      String?
  createdAt TimestamptzString @default(now())
}
```

### 2.3. Name the client after the database [#23-name-the-client-after-the-database]

Change `prisma/users/db.ts` to read `USERS_DATABASE_URL` and export a name you can tell apart from the second client:

```typescript title="prisma/users/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 usersDb = postgres<Contract>({ // [!code ++]
  contractJson,
  url: process.env.USERS_DATABASE_URL!, // [!code ++]
});
```

`contract.d.ts` and `contract.json` do not exist yet; you emit them in step 4.

## 3. Add the posts database [#3-add-the-posts-database]

The second database needs the same three files. Create them by hand; running `orm init` again would replace the files from step 2.

```bash
mkdir -p prisma/posts
```

```typescript title="prisma.posts.config.ts"
import "dotenv/config";
import { definePrismaConfig } from "prisma/config";
import { defineConfig as ormConfig } from "@prisma/orm-postgres/config";

export default definePrismaConfig({
  orm: ormConfig({
    contract: "./prisma/posts/contract.prisma",
    migrations: {
      dir: "./prisma/posts/migrations",
    },
    db: {
      connection: process.env.POSTS_DATABASE_URL!,
    },
  }),
});
```

```prisma title="prisma/posts/contract.prisma"
// use prisma-8

model Post {
  id        Int               @id @default(autoincrement())
  title     String
  content   String?
  authorId  Int
  createdAt TimestamptzString @default(now())
}
```

`authorId` is a plain integer, not a relation. A contract describes one database, and PostgreSQL cannot enforce a foreign key into another database, so the link between a post and its author is resolved in application code in step 5.

```typescript title="prisma/posts/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 postsDb = postgres<Contract>({
  contractJson,
  url: process.env.POSTS_DATABASE_URL!,
});
```

Now set both connection strings. Both config files and both clients load `.env` through `dotenv/config`, and Next.js loads it as well:

```bash title=".env"
USERS_DATABASE_URL="postgres://user:password@localhost:5432/users"
POSTS_DATABASE_URL="postgres://user:password@localhost:5432/posts"
```

`orm init` already added `.env` to `.gitignore`. Update `.env.example` with the two variable names so collaborators know what to set.

## 4. Emit the contracts and initialize both databases [#4-emit-the-contracts-and-initialize-both-databases]

Each Prisma ORM command works on one config. Add scripts that run every command once per database so you do not have to type `--config` twice:

```json title="package.json"
"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start",
  "contract:emit": "prisma contract emit --config ./prisma.users.config.ts && prisma contract emit --config ./prisma.posts.config.ts", // [!code ++]
  "db:init": "prisma db init --config ./prisma.users.config.ts && prisma db init --config ./prisma.posts.config.ts", // [!code ++]
  "db:verify": "prisma db verify --config ./prisma.users.config.ts && prisma db verify --config ./prisma.posts.config.ts", // [!code ++]
  "db:update": "prisma db update --config ./prisma.users.config.ts && prisma db update --config ./prisma.posts.config.ts", // [!code ++]
  "db:seed": "node prisma/seed.ts", // [!code ++]
  "prebuild": "npm run contract:emit" // [!code ++]
}
```

`prebuild` re-emits both contracts before every `next build`, locally and on Vercel, so the build never runs against stale artifacts.

Emit the contract artifacts for both databases. This step is offline:

  

#### bun

```bash
bun run contract:emit
```

#### pnpm

```bash
pnpm run contract:emit
```

#### yarn

```bash
yarn contract:emit
```

#### npm

```bash
npm run contract:emit
```

```text no-copy
✔ Emitted contract.json and contract.d.ts
│  contract:  prisma/users/contract.json
│  types:     prisma/users/contract.d.ts

✔ Emitted contract.json and contract.d.ts
│  contract:  prisma/posts/contract.json
│  types:     prisma/posts/contract.d.ts
```

Create the tables in both databases and sign each one with its contract:

  

#### bun

```bash
bun run db:init
```

#### pnpm

```bash
pnpm run db:init
```

#### yarn

```bash
yarn db:init
```

#### npm

```bash
npm run db:init
```

```text no-copy
│  contract:  prisma/users/contract.json
│  database:  postgres://****@localhost:5432/users

✔ Applied 2 operation(s) across 1 contract space

App space
├─ Create table "user"
├─ Add unique constraint on "user" (email)
└─ marker 3bb5103a83e513d042e9a53dec29c899f92b5209b3b88944fee251cbe86c0e9b

│  contract:  prisma/posts/contract.json
│  database:  postgres://****@localhost:5432/posts

✔ Applied 1 operation(s) across 1 contract space

App space
├─ Create table "post"
└─ marker 556eb8244a0431fc6f1483efacdf220c5daca7a0106048892be1142e807bced1
```

Each database now carries a marker with the hash of the contract it was initialized from. Confirm both match:

  

#### bun

```bash
bun run db:verify
```

#### pnpm

```bash
pnpm run db:verify
```

#### yarn

```bash
yarn db:verify
```

#### npm

```bash
npm run db:verify
```

```text no-copy
│  contract:  prisma/users/contract.json
│  database:  postgres://****@localhost:5432/users

✔ Database marker and schema match contract

│  contract:  prisma/posts/contract.json
│  database:  postgres://****@localhost:5432/posts

✔ Database marker and schema match contract
```

The marker is also what protects you from crossing the wires. Verifying the users contract against the posts database stops immediately:

  

#### bun

```bash
bunx prisma@latest db verify --config ./prisma.users.config.ts --db "$POSTS_DATABASE_URL"
```

#### pnpm

```bash
pnpm dlx prisma@latest db verify --config ./prisma.users.config.ts --db "$POSTS_DATABASE_URL"
```

#### yarn

```bash
yarn dlx prisma@latest db verify --config ./prisma.users.config.ts --db "$POSTS_DATABASE_URL"
```

#### npm

```bash
npx prisma@latest db verify --config ./prisma.users.config.ts --db "$POSTS_DATABASE_URL"
```

```text no-copy
✘ [CONTRACT.MARKER_MISMATCH] Hash mismatch
  why: Contract storageHash does not match database marker
```

## 5. Query both databases from the app [#5-query-both-databases-from-the-app]

### 5.1. Seed both databases [#51-seed-both-databases]

Create a script that writes through both clients. Node.js 24 runs TypeScript directly, so no extra tooling is needed:

```typescript title="prisma/seed.ts"
import { usersDb } from "./users/db.ts";
import { postsDb } from "./posts/db.ts";

async function main() {
  const alice = await usersDb.orm.public.User.create({
    email: "alice@prisma.io",
    name: "Alice",
  });
  const bob = await usersDb.orm.public.User.create({
    email: "bob@prisma.io",
    name: "Bob",
  });

  await postsDb.orm.public.Post.create({
    title: "Hello from the posts database",
    content: "This row lives in the posts database.",
    authorId: alice.id,
  });
  await postsDb.orm.public.Post.create({
    title: "Two databases, one app",
    content: null,
    authorId: bob.id,
  });

  console.log("Seeded", alice.email, "and", bob.email, "with one post each.");
  await usersDb.runtime().close();
  await postsDb.runtime().close();
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});
```

The `.ts` extensions in the imports are what Node.js needs to resolve the files. Allow them in `tsconfig.json`, otherwise `next build` fails its type check with `TS5097`:

```json title="tsconfig.json"
{
  "compilerOptions": {
    "allowImportingTsExtensions": true // [!code ++]
  }
}
```

Run the seed:

  

#### bun

```bash
bun run db:seed
```

#### pnpm

```bash
pnpm run db:seed
```

#### yarn

```bash
yarn db:seed
```

#### npm

```bash
npm run db:seed
```

```text no-copy
Seeded alice@prisma.io and bob@prisma.io with one post each.
```

Both clients open their connection pool on the first query. The script closes them at the end so the process can exit.

### 5.2. Render data from both databases [#52-render-data-from-both-databases]

Replace `app/page.tsx` with a server component that queries each client and joins posts to their authors in memory:

```tsx title="app/page.tsx"
import { usersDb } from "@/prisma/users/db";
import { postsDb } from "@/prisma/posts/db";

export const dynamic = "force-dynamic";

export default async function Home() {
  const users = await usersDb.orm.public.User.select("id", "email", "name").all();
  const posts = await postsDb.orm.public.Post
    .select("id", "title", "authorId")
    .orderBy((p) => p.id.asc())
    .all();

  const authorById = new Map(users.map((u) => [u.id, u]));

  return (
    <main className="mx-auto max-w-2xl p-8 font-sans">
      <h1 className="text-3xl font-bold">Multi-database showcase</h1>
      <p className="mt-2 text-zinc-600">
        Users come from one PostgreSQL database, posts from another.
      </p>

      <h2 className="mt-8 text-xl font-semibold">Users</h2>
      <ul className="mt-2 list-disc pl-6">
        {users.map((user) => (
          <li key={user.id}>
            {user.name} ({user.email})
          </li>
        ))}
      </ul>

      <h2 className="mt-8 text-xl font-semibold">Posts</h2>
      <ul className="mt-2 list-disc pl-6">
        {posts.map((post) => (
          <li key={post.id}>
            {post.title}, by {authorById.get(post.authorId)?.name ?? "unknown"}
          </li>
        ))}
      </ul>
    </main>
  );
}
```

Each client is typed by its own `contract.d.ts`: `usersDb.orm.public` only knows `User`, `postsDb.orm.public` only knows `Post`, and mixing them up is a type error. `force-dynamic` keeps Next.js from trying to render the page at build time, when no database is reachable.

### 5.3. Run the development server [#53-run-the-development-server]

  

#### bun

```bash
bun run dev
```

#### pnpm

```bash
pnpm run dev
```

#### yarn

```bash
yarn dev
```

#### npm

```bash
npm run dev
```

```text no-copy
▲ Next.js 16.3.4 (Turbopack)
- Local:         http://localhost:3000
- Environments: .env
✓ Ready in 894ms
```

Open [http://localhost:3000](http://localhost:3000). The page lists Alice and Bob under **Users** and the two posts under **Posts**, each attributed to its author. One request, two databases, no adapter or extra client package in between.

Finally, confirm the production build passes. `prebuild` emits both contracts first:

  

#### bun

```bash
bun run build
```

#### pnpm

```bash
pnpm run build
```

#### yarn

```bash
yarn build
```

#### npm

```bash
npm run build
```

```text no-copy
> prisma contract emit --config ./prisma.users.config.ts && prisma contract emit --config ./prisma.posts.config.ts
...
✓ Compiled successfully
```

## 6. Deploy to Vercel [#6-deploy-to-vercel]

The app needs two environment variables in production and nothing else that is Prisma-specific: `prebuild` runs on Vercel, and there is no client to generate and no engine binary to ship.

1. Push the project to a GitHub repository. If you do not have one yet, [create one on GitHub](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-new-repository), then run:
   ```bash
   git add .
   git commit -m "Next.js app with two Prisma 8 databases"
   git branch -M main
   git remote add origin https://github.com/<your-username>/<repository-name>.git
   git push -u origin main
   ```
2. In the [Vercel dashboard](https://vercel.com/), follow [Import an existing project](https://vercel.com/docs/getting-started-with-vercel/import) and stop at the step where you configure the project, before clicking **Deploy**.
3. Expand **Environment variables** and add both connection strings:
   * **Key**: `USERS_DATABASE_URL`, **Value**: the users database connection string from your `.env`
   * **Key**: `POSTS_DATABASE_URL`, **Value**: the posts database connection string from your `.env`
4. Click **Deploy**. Vercel runs `npm run build`, which emits both contracts and builds the app.

> [!WARNING]
> Set both variables before the first deploy. Each config file reads its variable with a non-null assertion, and a missing one fails the first request to the page.

Open the live URL. The page renders the same two lists from the same two databases. The Vercel deploy was not run while validating this guide; the `npm run build` it executes is the one from step 5.

## Common gotchas [#common-gotchas]

> [!WARNING]
> Commands without `--config` fail once the default config is gone. Running `npx prisma@latest contract emit` with no `./prisma.config.ts` (or with one that has no `orm` section) stops with `[CONFIG.FILE_NOT_FOUND]` and a hint that the `orm` config section is absent. Use the package scripts from step 4, or pass `--config` explicitly.

* **No relations across databases.** A contract covers one database. Keep a plain id column such as `authorId` on the side that references the other database, and join in application code.
* **`npx prisma@latest init` writes a third config.** It creates a `prisma.config.ts` that holds only the `skills` section for agent skills. That is expected; the database commands keep using `--config` with the two database configs.
* **Do not close a client per request.** In `page.tsx` and route handlers, never call `usersDb.runtime().close()`. The pools are shared across requests and close when the process exits. Only scripts such as `prisma/seed.ts` close them.
* **`orm init` picks the package manager from your lockfile.** Run it after `create-next-app` has written `package-lock.json`, `pnpm-lock.yaml`, or `bun.lock`, or it may install with a different package manager than the one you use.

## 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, add a `Comment` model to `prisma/posts/contract.prisma`, emit with `--config ./prisma.posts.config.ts`, and update the posts database."
* "Add a third database for billing following the same layout: `prisma.billing.config.ts`, `prisma/billing/contract.prisma`, `prisma/billing/db.ts`, and extend the package scripts."
* "Write a route handler that creates a post for the signed-in user, validating that the `authorId` exists in the users database first."

## Next steps [#next-steps]

* Change a contract, then run `npm run contract:emit` and `npm run db:update` to apply the change to both databases in development. For checked-in migrations, run [`migration plan`](https://www.prisma.io/docs/cli/migration-plan) and [`db migrate`](https://www.prisma.io/docs/cli/db-migrate) with the `--config` of the database you changed; each writes to its own `migrations.dir`.
* [Learn the fundamentals](https://www.prisma.io/docs/orm/fundamentals/reading-data): filtering, sorting, pagination, and writes.
* [CLI configuration](https://www.prisma.io/docs/cli/configuration) covers everything `prisma.config.ts` can hold.
* Splitting the app itself into packages? See [pnpm workspaces](https://www.prisma.io/docs/guides/deployment/pnpm-workspaces) and [Turborepo](https://www.prisma.io/docs/guides/deployment/turborepo).

## Related pages

- [`Expand-and-contract migrations`](https://www.prisma.io/docs/guides/database/data-migration): Replace a column without downtime using the expand and contract pattern, with the data backfill inside a Prisma ORM migration.
- [`Schema management in teams`](https://www.prisma.io/docs/guides/database/schema-changes): Plan, merge, and apply Prisma ORM migrations when several developers change the schema at the same time.