# pnpm workspaces (/docs/guides/deployment/pnpm-workspaces) > 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. Set up Prisma 8 in a shared database package inside a pnpm workspaces monorepo and query it from a Next.js app. Location: Guides > Deployment > pnpm workspaces ## Introduction [#introduction] This guide shows you how to set up Prisma 8 in its own package inside a [pnpm workspaces](https://pnpm.io/workspaces) monorepo. The `database` package owns the contract, the emitted types, and the client. A Next.js app in the same workspace imports that client and renders users from the database. Every command and output below was run end to end with pnpm 12 against a PostgreSQL database. > [!NOTE] > Using Prisma 7? > > Prisma 8 is the current release of Prisma ORM. Prisma 7 remains fully supported; the Prisma 7 version of this guide is at [/guides/v7/deployment/pnpm-workspaces](https://www.prisma.io/docs/guides/v7/deployment/pnpm-workspaces). ## Prerequisites [#prerequisites] * [Node.js](https://nodejs.org) 24 or later * [pnpm](https://pnpm.io/installation) 10.26 or later, but not 12.0 to 12.4.0, which have a defect that breaks step 2.1 (this guide uses pnpm 12.7.0) * A PostgreSQL connection string, or nothing at all: `npx create-db@latest` can create a [Prisma Postgres](https://www.prisma.io/docs/postgres) database for you ## 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 Set up a pnpm workspaces monorepo with a shared Prisma 8 database package and a Next.js app that renders users from it. 1. Create `my-monorepo` with `pnpm init`, a `pnpm-workspace.yaml` listing `apps/*` and `packages/*` with `allowBuilds` for `esbuild`, `msgpackr-extract`, and `workerd`, and the directories `apps` and `packages/database`. 2. In `packages/database`, run `pnpm init`, then `npx prisma@latest orm init --yes --target postgres --authoring psl`. Then run `pnpm prisma init` in the same directory so the Prisma agent skills are installed, and use them. 3. Write `packages/database/.env` with `DATABASE_URL` (use the connection string I give you, or create a Prisma Postgres database with `npx create-db@latest` and show me the claim URL it prints). Run `pnpm prisma db init` in `packages/database`. 4. Add `src/index.ts` exporting `db` from `./prisma/db`, set `"exports": { ".": "./src/index.ts" }` in the package's package.json, add a `src/seed.ts` that upserts two users with `db.orm.public.User.upsert(...)` and closes with `await db.close()`, and run it with `node src/seed.ts`. 5. In `apps`, run `pnpm create next-app@latest web --yes --skip-install`, delete `apps/web/.git` and `apps/web/pnpm-workspace.yaml`, add `sharp: false` and `unrs-resolver: false` to `allowBuilds` in the root `pnpm-workspace.yaml`, add `"database": "workspace:*"` to `apps/web/package.json`, copy `packages/database/.env` to `apps/web/.env`, and run `pnpm install` from the workspace root. 6. Replace `apps/web/app/page.tsx` with a server component that imports `{ db } from "database"`, exports `dynamic = "force-dynamic"`, queries `db.orm.public.User.select("id", "email", "name").all()`, and renders the list. 7. Add root scripts `dev`, `build`, `start`, `db:init`, `db:update`, and `seed` that filter to the right package, start `pnpm dev` in the background, verify http://localhost:3000 renders the seeded users, then stop it. Finally run `pnpm build` and confirm it completes. ``` ## 1. Create the workspace [#1-create-the-workspace] Create the monorepo directory and initialize it: ```bash mkdir my-monorepo cd my-monorepo pnpm init ``` pnpm 12 writes a root `package.json` with `"type": "module"` and a pinned `packageManager` field. Next, create `pnpm-workspace.yaml`: ```yaml title="pnpm-workspace.yaml" packages: - "apps/*" - "packages/*" allowBuilds: esbuild: true msgpackr-extract: true workerd: true ``` The `allowBuilds` block matters. pnpm does not run dependency install scripts unless you approve them. Since pnpm 11 an unapproved script fails the install (`strictDepBuilds` is on by default); pnpm 10 only warns and skips the script, which leaves the package half-installed, unless you set `strictDepBuilds: true`. The Prisma ORM 8 CLI's toolchain pulls in three packages with install scripts, so approve them up front. The `allowBuilds` key exists since pnpm 10.26, so use that version or later; without the key, the first `pnpm add` that `orm init` runs fails with `ERR_PNPM_IGNORED_BUILDS` on pnpm 11 (on pnpm 10 with the default settings it installs the three packages without running their scripts). Create the directories for apps and shared packages: ```bash mkdir -p apps packages/database ``` ## 2. Set up the shared database package [#2-set-up-the-shared-database-package] The `database` package holds the contract (your schema), the emitted `contract.json` and `contract.d.ts`, and the `db` client every app imports. Prisma 8 has no `prisma generate` step and no generated client directory: `contract emit` writes the two artifacts next to the contract, and the runtime reads them. ### 2.1. Initialize Prisma 8 in the package [#21-initialize-prisma-8-in-the-package] ```bash cd packages/database pnpm init npx prisma@latest orm init --target postgres ``` Answer the prompts: choose `PSL` for the authoring style and keep the other defaults, including the schema path `src/prisma/contract.prisma`. `orm init` detects pnpm from the workspace, adds the dependencies to this package, and writes the Prisma 8 files: ```text no-copy ▸ pnpm add @prisma/orm-postgres dotenv ✔ pnpm add @prisma/orm-postgres dotenv ▸ pnpm add -D prisma@latest @types/node ✔ pnpm add -D prisma@latest @types/node ▸ pnpm add -D @prisma/cli-engine@0.6.3 ✔ pnpm add -D @prisma/cli-engine@0.6.3 ▸ Emit the contract ✔ Emit the contract │ target: postgres │ 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 ``` On pnpm 12.0 to 12.4.0, `orm init` writes all the files and then fails at `Emit the contract` with `CLI.INIT_EMIT_FAILED`. If you hit it, apply the `pnpm dedupe` fix from [Common gotchas](#common-gotchas) and continue. The `@prisma/cli-engine` version comes from the `prisma` package the previous step installed, so the two always match. `orm init` emits the contract itself, so `src/prisma/contract.json` and `src/prisma/contract.d.ts` exist as soon as it finishes. After every schema edit, re-emit with the package's own CLI: ```bash pnpm prisma contract emit ``` `orm init` wrote a starter contract with `User` and `Post` models in `src/prisma/contract.prisma`, and a client in `src/prisma/db.ts`: ```typescript title="packages/database/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']!, }); ``` There is no driver adapter and no engine to configure. The `with { type: 'json' }` import attribute is required by Node's ESM loader; pnpm's `pnpm init` already set `"type": "module"` on the package, which is what the generated files expect. If your package declares `"type": "commonjs"`, `orm init` leaves it alone and prints a warning; change it to `"module"`. ### 2.2. Connect the database [#22-connect-the-database] `prisma.config.ts` loads `.env` through `dotenv/config` and reads `DATABASE_URL`. Create `.env` in the package with your connection string, or the one `npx create-db@latest` prints: ```bash title="packages/database/.env" DATABASE_URL="postgres://user:password@localhost:5432/mydb" ``` Apply the contract to the database and sign it: ```bash pnpm prisma db init ``` ```text no-copy ✔ Initialising database across spaces │ contract: src/prisma/contract.json │ database: postgres://****@localhost:5432/mydb ✔ Applied 5 operation(s) across 1 contract space App space ├─ Create table "Post" ├─ Create table "User" ├─ Add unique constraint on "User" (email) ├─ Create index "Post_authorId_idx_e47547ed" on "Post" ├─ Add foreign key "Post_authorId_fkey" on "Post" └─ marker 91e7f9f035806fa2789a4d726ef7724cad434fd6b00014d47ebf12d6e6bb784e ✔ Advanced ref "db" → 91e7f9f035806fa2789a4d726ef7724cad434fd6b00014d47ebf12d6e6bb784e ``` If `db init` stops with `Connection terminated unexpectedly`, a database you just created is still starting; wait a few seconds and run it again. The command is safe to repeat and reports `Database already matches contract` when there is nothing left to do. ### 2.3. Export the client and add a seed script [#23-export-the-client-and-add-a-seed-script] Create the package entry point that apps will import: ```typescript title="packages/database/src/index.ts" export { db } from "./prisma/db"; ``` Add a seed script so the app has rows to render. Node.js 24 runs TypeScript directly, but it needs the `.ts` extension on relative imports, so this file names it: ```typescript title="packages/database/src/seed.ts" import { db } from "./prisma/db.ts"; const users = [ { email: "alice@prisma.io", name: "Alice" }, { email: "bob@prisma.io", name: "Bob" }, ]; for (const user of users) { await db.orm.public.User.upsert({ create: user, update: {}, conflictOn: { email: user.email }, }); } console.log(await db.orm.public.User.select("id", "email", "name").all()); await db.close(); ``` Point the package at the entry point and add scripts for the database steps. Replace the `main` field `pnpm init` wrote with an `exports` map, and drop the placeholder `test` script: ```json title="packages/database/package.json" { "name": "database", "version": "1.0.0", "type": "module", "exports": { ".": "./src/index.ts" }, "scripts": { "contract:emit": "prisma contract emit", "db:init": "prisma db init", "db:update": "prisma db update", "seed": "node src/seed.ts" } } ``` Keep the `dependencies` and `devDependencies` that `orm init` added. Run the seed: ```bash pnpm seed ``` ```text no-copy $ node src/seed.ts [ { id: 1, email: 'alice@prisma.io', name: 'Alice' }, { id: 2, email: 'bob@prisma.io', name: 'Bob' } ] ``` The script resolves `@prisma/orm-postgres` through pnpm's isolated `node_modules` without any hoisting configuration, because the package declares it as a direct dependency. That is the rule to keep in mind for the rest of the workspace: the `database` package depends on `@prisma/orm-postgres`, and apps depend on `database`. ## 3. Set up the Next.js app [#3-set-up-the-nextjs-app] ### 3.1. Scaffold the app into the workspace [#31-scaffold-the-app-into-the-workspace] ```bash cd ../../apps pnpm create next-app@latest web --yes --skip-install ``` `--yes` accepts the defaults (App Router, TypeScript, Tailwind CSS, no `src/` directory). `--skip-install` keeps `create-next-app` from installing into a nested `node_modules`; the workspace root installs for every package. The scaffold still writes two files that belong to the root, so remove them: ```bash rm -rf web/.git web/pnpm-workspace.yaml ``` The `pnpm-workspace.yaml` you removed told pnpm not to run the install scripts of two of the app's dependencies. Move that decision to the root file, or the `pnpm install` below fails with `ERR_PNPM_IGNORED_BUILDS`: ```yaml title="pnpm-workspace.yaml" packages: - "apps/*" - "packages/*" allowBuilds: esbuild: true msgpackr-extract: true sharp: false # [!code ++] unrs-resolver: false # [!code ++] workerd: true ``` Add the shared package as a dependency of the app: ```json title="apps/web/package.json" "dependencies": { "database": "workspace:*", // [!code ++] "next": "16.3.6", "react": "19.2.8", "react-dom": "19.2.8" } ``` Next.js reads `.env` from the app directory, and `db.ts` reads it from the working directory of the dev server, which is the same place. Copy the file from the database package: ```bash cp ../packages/database/.env web/.env ``` Install from the workspace root so pnpm links `database` into the app: ```bash cd .. pnpm install ``` ```text no-copy Scope: all 3 workspace projects Progress: resolved 700, reused 700, downloaded 0, added 700, done Done in 1.9s using pnpm v12.7.0 ``` ### 3.2. Render users from the shared package [#32-render-users-from-the-shared-package] Replace `apps/web/app/page.tsx` with a server component that queries through the shared client: ```tsx title="apps/web/app/page.tsx" import { db } from "database"; export const dynamic = "force-dynamic"; export default async function Home() { const users = await db.orm.public.User.select("id", "email", "name").all(); return (

Users

{users.length === 0 ? (

No users in the database yet.

) : ( )}
); } ``` Model access is namespace-qualified on PostgreSQL: `db.orm.public.User`. `force-dynamic` makes Next.js query on each request instead of at build time, so `pnpm build` does not need a reachable database. Next.js compiles the package's TypeScript source through the `exports` map; no `transpilePackages` entry is needed. ### 3.3. Add root scripts [#33-add-root-scripts] Add scripts to the root `package.json` that run each step in the right package. `build` re-emits the contract before the app builds, so the types the app compiles against always match `contract.prisma`: ```json title="package.json" "scripts": { "dev": "pnpm --filter web dev", "build": "pnpm --filter database contract:emit && pnpm --filter web build", "start": "pnpm --filter web start", "db:init": "pnpm --filter database db:init", "db:update": "pnpm --filter database db:update", "seed": "pnpm --filter database seed" } ``` ### 3.4. Run the app [#34-run-the-app] From the workspace root: ```bash pnpm dev ``` ```text no-copy $ next dev ▲ Next.js 16.3.6 (Turbopack) - Local: http://localhost:3000 - Environments: .env ✓ Ready in 479ms ``` Open [http://localhost:3000](http://localhost:3000) (set `PORT` to change it). The page lists Alice and Bob, rendered by a server component calling Prisma 8 through the `database` package. ## 4. Build for production [#4-build-for-production] ```bash pnpm build ``` ```text no-copy $ prisma contract emit $ next build ▲ Next.js 16.3.6 (Turbopack) ✓ Compiled successfully in 1026ms Running TypeScript ... Finished TypeScript in 2.2s ... ✓ Generating static pages using 5 workers (3/3) in 519ms Route (app) ┌ ƒ / └ ○ /_not-found ``` The type check runs across the package boundary: `next build` type-checks `packages/database/src/index.ts` along with the app. Then serve the build: ```bash pnpm start ``` The page renders the same users from the production server. ## 5. (Optional) Browse your data in Prisma Studio [#5-optional-browse-your-data-in-prisma-studio] [Prisma Studio](https://www.prisma.io/docs/studio) ships with the Prisma 7 CLI and connects to a Prisma 8 database through `--url`. Run it from the workspace root, which has no `prisma.config.ts` for the Prisma 7 CLI to trip over: ```bash pnpm dlx prisma@prev studio --url "postgres://user:password@localhost:5432/mydb" ``` ```text no-copy Prisma Studio is running at: http://localhost:51212 ``` Open the URL Studio prints. The `User` and `Post` tables appear under **Tables**, with the seeded rows ready to edit. See [Studio with Prisma 8](https://www.prisma.io/docs/studio/prisma-next) for the migration history view. ## Common gotchas [#common-gotchas] > [!WARNING] > `pnpm dlx prisma@latest` stops at an interactive **Choose which packages to build** prompt, because `dlx` runs outside the workspace and ignores its `allowBuilds`. Either answer the prompt, or run `npx prisma@latest` from a package directory as this guide does. At the workspace root, `npx` itself fails with `EBADDEVENGINES`: pnpm 12's `pnpm init` writes a `devEngines.packageManager` field that npm enforces. Use the package's own CLI (`pnpm prisma ...`) or `pnpm dlx` there. * If `orm init` ends with `CLI.INIT_EMIT_FAILED`, or `pnpm prisma contract emit` reports `CLI.CONFIG_UNREADABLE`, and the message says `Cannot find module '@prisma/cli-engine'` or `No "exports" main defined`, the `@prisma/cli-engine` link in `packages/database/node_modules` points at a directory that does not exist. This is a defect in pnpm 12.0 to 12.4.0, and pnpm 12.4.1 or later fixes it. In a project that already hit it, run `pnpm dedupe` to rebuild the link, then `pnpm prisma contract emit`. You do not need to run `orm init` again, because it wrote all its files before it tried to emit. `pnpm install`, `pnpm install --force`, and adding the packages again do not repair the link. * Every Prisma command reads `DATABASE_URL` from `packages/database/.env` through `prisma.config.ts`, and the app reads `apps/web/.env`. Keep the two files in sync, or export the variable in your shell and drop both files. * Do not call `db.close()` in a page or route handler. The client is a module-level singleton whose connection pool is shared across requests; close it only in scripts that exit, like `seed.ts`. * After you change `src/prisma/contract.prisma`, run `pnpm --filter database contract:emit` so the app sees the new types, then `pnpm db:update` to apply the change. The root `build` script emits for you before every build. ## Prompt your coding agent [#prompt-your-coding-agent] Run [`pnpm prisma init`](https://www.prisma.io/docs/cli/init) once inside `packages/database` to install the [Prisma 8 skills](https://www.prisma.io/docs/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. It adds a `postinstall` script that keeps the skills matching your installed packages, and it installs them into `.claude/skills`, `.cursor/skills`, `.agents/skills`, and `.devin/skills` under the package. Prompts that map to this guide: * "Using the prisma-8 skill, add a `Post` list under each user on the home page with `.include('posts')`." * "Add a `packages/database` script that creates a post for a given user email." * "Add a `role` enum to the `User` model in `contract.prisma`, emit the contract, and update the database." ## Next steps [#next-steps] You now have a pnpm workspace where one package owns the Prisma 8 contract and client, and a Next.js app that renders through it. * To add task orchestration and caching on top of this setup, see the [Turborepo](https://www.prisma.io/docs/guides/deployment/turborepo) guide. * [Learn the fundamentals](https://www.prisma.io/docs/orm/fundamentals/reading-data): filtering, sorting, pagination, and writes. * [Read the Prisma 8 overview](https://www.prisma.io/docs/orm) for the concepts behind contracts and typed queries. * Use [migration plan](https://www.prisma.io/docs/cli/migration-plan) and [db migrate](https://www.prisma.io/docs/cli/db-migrate) when you want checked-in migrations instead of `db update`. ## Related pages - [`Bun workspaces`](https://www.prisma.io/docs/guides/deployment/bun-workspaces): Set up Prisma ORM in a Bun workspaces monorepo through a shared database package, seed it with Bun, and query it from a Next.js app in the same workspace. - [`Cloudflare Workers`](https://www.prisma.io/docs/guides/deployment/cloudflare-workers): Add Prisma ORM to a Cloudflare Worker, query PostgreSQL from the fetch handler with the nodejs_compat flag, and deploy it with Wrangler. - [`Docker`](https://www.prisma.io/docs/guides/deployment/docker): Build an Express app on Prisma ORM, run PostgreSQL from Docker Compose, then run the app and the database together in containers. - [`Turborepo`](https://www.prisma.io/docs/guides/deployment/turborepo): Share one Prisma 8 database package across the apps in a Turborepo monorepo, with contract emit and migrations wired into turbo tasks.