# GitHub Actions (/docs/guides/integrations/github-actions) > 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. Provision a Prisma Postgres database for every pull request with GitHub Actions and the Prisma CLI, apply your migrations and seed data to it, and delete it when the pull request closes. Location: Guides > Integrations > GitHub Actions ## Introduction [#introduction] This guide shows you how to create and delete [Prisma Postgres](https://www.prisma.io/docs/postgres) databases from GitHub Actions with the Prisma CLI. The workflow provisions a database for every pull request, applies your checked-in migrations, seeds it with sample data, and leaves a comment on the pull request with the database name and status. ![GitHub Actions comment](https://www.prisma.io/img/guides/github-comment.png) When the pull request is closed, the workflow deletes the database. Every pull request gets its own isolated database, so migrations and data changes can be tested without touching a shared development database. The project setup, migrations, seed script, and the `postgres create`, `postgres list`, and `postgres connection create` commands below were run against a live Prisma Postgres database. The workflow file is assembled from those commands. > [!NOTE] > Using Prisma ORM 7? > > Prisma ORM 8 is a [release candidate](https://www.prisma.io/docs/orm/release-status). Prisma ORM 7 remains fully supported; the Prisma ORM 7 version of this guide is at [/guides/v7/integrations/github-actions](https://www.prisma.io/docs/guides/v7/integrations/github-actions). ## Prerequisites [#prerequisites] * [Node.js](https://nodejs.org) 22.18 or newer on the 22 line, 24.11 or newer on the 24 line, or 26 or newer (24 recommended) * A [Prisma Data Platform](https://console.prisma.io?utm_source=actions_guide\&utm_medium=docs) account * A GitHub repository * `jq`, to read the CLI's JSON output (GitHub-hosted runners have it preinstalled) ## 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 per-pull-request Prisma Postgres databases for this project with GitHub Actions and Prisma ORM. 1. Add Prisma ORM to the project with `npx prisma@latest orm init --yes --target postgres --authoring psl` (run it after a lockfile exists), then run `npx prisma@latest init` so the Prisma agent skills are installed and stay current, and use them. Define User and Post models in `src/prisma/contract.prisma`, run `npx prisma contract emit`, then `npx prisma migration plan --name init` and `npx prisma db migrate` against the DATABASE_URL I give you (or one from `npx create-db@latest`). 2. Write `src/prisma/seed.ts` that creates two users with posts through `db.orm.public.User.create` and `db.orm.public.Post.create`, skips when users already exist (use `.aggregate((a) => ({ total: a.count() }))`, not `.count()`), and ends with `await db.close()`. Add a `seed` script and verify `npm run seed` works twice. 3. Check `npx prisma auth whoami`; if I am not signed in, stop and ask me to run `npx prisma auth login`. Create a dedicated platform project with `npx prisma project create ` and show me the project id. 4. Write `.github/workflows/prisma-postgres-preview.yml` following https://www.prisma.io/docs/guides/integrations/github-actions.md: a provision job that finds or creates a database named after the pull request with `npx prisma postgres list|create --project "$PRISMA_PROJECT_ID" --json`, reads `.envelope.result.connectionString` with jq, runs `db migrate --db`, `db verify --db`, and `npm run seed`, then comments on the pull request; and a cleanup job that deletes the database with `postgres delete --confirm ` when the pull request closes. Always pass `--project`; the CLI does not read PRISMA_PROJECT_ID from the environment for these commands. 5. Tell me which GitHub secrets to add: PRISMA_SERVICE_TOKEN, PRISMA_WORKSPACE_ID, PRISMA_PROJECT_ID. ``` ## 1. Set up the project [#1-set-up-the-project] Create a project and make it an ES module: #### bun ```bash mkdir prisma-gha-demo && cd prisma-gha-demo bun init npm pkg set type module # couldn't auto-convert command bun install ``` #### pnpm ```bash mkdir prisma-gha-demo && cd prisma-gha-demo pnpm init npm pkg set type=module # couldn't auto-convert command pnpm install ``` #### yarn ```bash mkdir prisma-gha-demo && cd prisma-gha-demo yarn init npm pkg set type=module # couldn't auto-convert command yarn install ``` #### npm ```bash mkdir prisma-gha-demo && cd prisma-gha-demo npm init npm pkg set type=module npm install ``` The empty `npm install` writes a `package-lock.json`. `orm init` picks the package manager from the lockfile it finds, so creating one first keeps the setup on npm. ## 2. Add Prisma ORM [#2-add-prisma-orm] In this step you add Prisma ORM to the project, define the data model, create the first migration, and seed the database locally. Everything you do here runs again inside GitHub Actions in step 4, against a fresh database. ### 2.1. Initialize Prisma ORM [#21-initialize-prisma-orm] #### bun ```bash bunx prisma@latest orm init --yes --target postgres --authoring psl ``` #### pnpm ```bash pnpm dlx prisma@latest orm init --yes --target postgres --authoring psl ``` #### yarn ```bash yarn dlx prisma@latest orm init --yes --target postgres --authoring psl ``` #### npm ```bash npx prisma@latest orm init --yes --target postgres --authoring psl ``` This writes `prisma.config.ts`, `src/prisma/contract.prisma`, `src/prisma/db.ts`, `.env.example`, `tsconfig.json`, and `prisma-8.md`, installs `@prisma/orm-postgres` and `dotenv` plus the `prisma`, `@types/node`, and `@prisma/cli-engine` dev dependencies, and emits `src/prisma/contract.json` and `src/prisma/contract.d.ts`. Those two emitted files are the whole client. There is no generated client package, no `prisma generate`, and no driver adapter to install: the runtime in `src/prisma/db.ts` reads `contract.json` and connects with the `DATABASE_URL` it finds in `.env`. ```typescript title="src/prisma/db.ts" no-copy import 'dotenv/config'; import postgres from '@prisma/orm-postgres/runtime'; import { contractFromJson } from '@prisma/orm-postgres/static'; import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; export const contract = contractFromJson(contractJson); export const db = postgres({ contract, url: process.env['DATABASE_URL']!, }); ``` ### 2.2. Define the contract [#22-define-the-contract] `orm init` starts you with `User` and `Post` models. Add a `published` flag to `Post`: ```prisma title="src/prisma/contract.prisma" // use prisma-8 model User { id Int @id @default(autoincrement()) email String @unique username String? name String? posts Post[] createdAt TimestamptzString @default(now()) updatedAt temporal.updatedAtString() } model Post { id Int @id @default(autoincrement()) title String content String? published Boolean @default(false) // [!code ++] author User @relation(fields: [authorId], references: [id]) authorId Int createdAt TimestamptzString @default(now()) updatedAt temporal.updatedAtString() } ``` Emit the contract again so `contract.json` and the types match: #### bun ```bash bunx prisma contract emit ``` #### pnpm ```bash pnpm prisma contract emit ``` #### yarn ```bash yarn prisma contract emit ``` #### npm ```bash npx prisma contract emit ``` ```text no-copy ✔ Emitted contract.json and contract.d.ts storageHash: 0c3a18eb65d5a5027c444cf0626d843cdb41dfd5fe8e66d6fce8d2eab460c971 executionHash: 4e88564095b5e0ba7904ffdba3c563ef916d87688c8c483c67055f71a9194f0f profileHash: 3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2 ``` ### 2.3. Connect a development database [#23-connect-a-development-database] Copy `.env.example` to `.env` and set `DATABASE_URL` to a PostgreSQL database you can use for development. A local PostgreSQL works, or create a Prisma Postgres database with `npx create-db@latest` and paste the connection string it prints: ```bash title=".env" DATABASE_URL="postgres://user:password@localhost:5432/prisma_gha_demo" ``` `prisma.config.ts` loads `.env`, so every CLI command below reads this value. The workflow overrides it per run with `--db`. ### 2.4. Plan and apply the first migration [#24-plan-and-apply-the-first-migration] The workflow applies migrations that are committed to the repository, so create the first one now: #### bun ```bash bunx prisma migration plan --name init ``` #### pnpm ```bash pnpm prisma migration plan --name init ``` #### yarn ```bash yarn prisma migration plan --name init ``` #### npm ```bash npx prisma migration plan --name init ``` ```text no-copy ✔ Planned 6 operation(s) migrations/app/20260910T1549_init ├─ Create schema "public" ├─ 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" from: (baseline) to: 0c3a18eb65d5a5027c444cf0626d843cdb41dfd5fe8e66d6fce8d2eab460c971 app space: migrations/app/20260910T1549_init ``` `migration plan` is offline: it writes a migration directory under `migrations/app/` and prints a DDL preview, and it does not touch the database. Apply it to your development database: #### bun ```bash bunx prisma db migrate ``` #### pnpm ```bash pnpm prisma db migrate ``` #### yarn ```bash yarn prisma db migrate ``` #### npm ```bash npx prisma db migrate ``` ```text no-copy ✔ Applied 1 migration(s) (6 operation(s)) across 1 contract space(s) App space ├─ Create schema "public" ├─ 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 0c3a18eb65d5a5027c444cf0626d843cdb41dfd5fe8e66d6fce8d2eab460c971 ``` Commit the `migrations/` directory, and whenever you change the contract, run `contract emit`, `migration plan --name `, and `db migrate` again; the pull request's database then receives exactly the migrations the pull request adds. See [How migrations work](https://www.prisma.io/docs/orm/migrations/how-migrations-work) for the full loop. ### 2.5. Seed the database [#25-seed-the-database] Create `src/prisma/seed.ts` with the code below. `User.create()` returns the user it created, and the script uses that user's `id` when it creates the user's posts: ```typescript title="src/prisma/seed.ts" import { db } from "./db.ts"; const users = [ { name: "Alice", email: "alice@prisma.io", posts: [ { title: "Join the Prisma Discord", content: "https://pris.ly/discord", published: true }, { title: "Prisma on YouTube", content: "https://pris.ly/youtube", published: false }, ], }, { name: "Bob", email: "bob@prisma.io", posts: [ { title: "Follow Prisma on Twitter", content: "https://twitter.com/prisma", published: true }, ], }, ]; async function main() { const { total } = await db.orm.public.User.aggregate((a) => ({ total: a.count() })); if (total > 0) { console.log(`Database already has ${total} users, skipping seed`); return; } for (const { posts, ...user } of users) { const created = await db.orm.public.User.create(user); for (const post of posts) { await db.orm.public.Post.create({ ...post, authorId: created.id }); } console.log(`Seeded ${created.email} with ${posts.length} posts`); } } main() .catch((error) => { console.error(error); process.exitCode = 1; }) .finally(() => db.close()); ``` The guard at the top makes the script safe to run twice, which matters when a pull request is reopened and the workflow runs against a database that already has data. The `db.close()` at the end releases the connection pool; without it the process keeps running after the seed finishes. Add a script for it: ```json title="package.json" { "scripts": { "contract:emit": "prisma contract emit", "seed": "node src/prisma/seed.ts" // [!code ++] } } ``` Node.js runs the TypeScript file directly. Run it: #### bun ```bash bun run seed ``` #### pnpm ```bash pnpm run seed ``` #### yarn ```bash yarn seed ``` #### npm ```bash npm run seed ``` ```text no-copy Seeded alice@prisma.io with 2 posts Seeded bob@prisma.io with 1 posts ``` Run it again and the guard reports `Database already has 2 users, skipping seed`. To check the data, put a query in `query.ts` and run it with `node query.ts`: ```typescript title="query.ts" import { db } from "./src/prisma/db.ts"; const users = await db.orm.public.User.select("id", "email", "name") .include("posts", (post) => post.select("title", "published")) .all(); console.log(JSON.stringify(users, null, 2)); await db.close(); ``` ```json no-copy [ { "id": 1, "email": "alice@prisma.io", "name": "Alice", "posts": [ { "title": "Join the Prisma Discord", "published": true }, { "title": "Prisma on YouTube", "published": false } ] }, { "id": 2, "email": "bob@prisma.io", "name": "Bob", "posts": [{ "title": "Follow Prisma on Twitter", "published": true }] } ] ``` The project now works locally, so next set up the platform side that the workflow will drive. ## 3. Create a platform project for preview databases [#3-create-a-platform-project-for-preview-databases] The Prisma CLI manages Prisma Postgres databases with the [`postgres`](https://www.prisma.io/docs/cli/postgres) commands. Databases live inside a platform [project](https://www.prisma.io/docs/cli/project), so create a dedicated project for the workflow. That keeps preview databases away from your development databases and gives the workflow a single project id to target. Sign in once (it opens a browser): #### bun ```bash bunx prisma auth login ``` #### pnpm ```bash pnpm prisma auth login ``` #### yarn ```bash yarn prisma auth login ``` #### npm ```bash npx prisma auth login ``` Create the project from your project directory: #### bun ```bash bunx prisma project create prisma-gha-preview ``` #### pnpm ```bash pnpm prisma project create prisma-gha-preview ``` #### yarn ```bash yarn prisma project create prisma-gha-preview ``` #### npm ```bash npx prisma project create prisma-gha-preview ``` This creates the project in your workspace and links the directory to it by writing `.prisma/local.json`, which it also adds to `.gitignore`. Show the link to get the project id: #### bun ```bash bunx prisma project show ``` #### pnpm ```bash pnpm prisma project show ``` #### yarn ```bash yarn prisma project show ``` #### npm ```bash npx prisma project show ``` ```text no-copy ℹ This directory is linked to the following platform project. local repo: ~/prisma-gha-demo platform: Prisma Sandbox / prisma-gha-preview url: https://api.prisma.io/v1/projects/proj_abc123def456 ``` The `proj_...` segment at the end of the URL is the project id. Keep it; you store it as a GitHub secret in step 6. Now try the exact commands the workflow runs. Create a database and ask for JSON output: #### bun ```bash bunx prisma postgres create pr-test --project proj_abc123def456 --json ``` #### pnpm ```bash pnpm prisma postgres create pr-test --project proj_abc123def456 --json ``` #### yarn ```bash yarn prisma postgres create pr-test --project proj_abc123def456 --json ``` #### npm ```bash npx prisma postgres create pr-test --project proj_abc123def456 --json ``` ```json no-copy { "kind": "result", "envelope": { "ok": true, "commandId": "postgres.create", "result": { "projectId": "proj_abc123def456", "projectName": "prisma-gha-preview", "database": { "id": "db_abc123def456", "name": "pr-test", "region": "us-east-1", "status": "ready" }, "connection": { "id": "con_abc123def456", "name": "Prisma Postgres API Key" }, "connectionString": "postgres://@pooled.db.prisma.io:5432/postgres?sslmode=require" } } } ``` The connection string is shown once, in this result, and never again. In `--json` mode every command prints newline-delimited events and ends with one `"kind": "result"` line whose `envelope` carries `ok`, `result`, and on failure `error.code`. The workflow reads the connection string with `jq` from that line. Apply the migration and seed the new database by passing the connection string explicitly: ```bash export PREVIEW_URL="" npx prisma db migrate --db "$PREVIEW_URL" npx prisma db verify --db "$PREVIEW_URL" DATABASE_URL="$PREVIEW_URL" npm run seed ``` ```text no-copy ✔ Applied 1 migration(s) (6 operation(s)) across 1 contract space(s) ✔ Database marker and schema match contract Seeded alice@prisma.io with 2 posts Seeded bob@prisma.io with 1 posts ``` `--db` overrides the `DATABASE_URL` from `.env` for the CLI, and the environment variable in front of `npm run seed` does the same for the seed script. List the project's databases to see it: #### bun ```bash bunx prisma postgres list --project proj_abc123def456 ``` #### pnpm ```bash pnpm prisma postgres list --project proj_abc123def456 ``` #### yarn ```bash yarn prisma postgres list --project proj_abc123def456 ``` #### npm ```bash npx prisma postgres list --project proj_abc123def456 ``` ```text no-copy project: prisma-gha-preview Name Branch Region Status Id pr-test br_abc123def456 us-east-1 ready db_abc123def456 ``` Deleting a database is destructive, so `postgres delete` asks you to type the database id. In a script there is nobody to type it, so pass it with `--confirm`: #### bun ```bash bunx prisma postgres delete db_abc123def456 --project proj_abc123def456 --confirm db_abc123def456 ``` #### pnpm ```bash pnpm prisma postgres delete db_abc123def456 --project proj_abc123def456 --confirm db_abc123def456 ``` #### yarn ```bash yarn prisma postgres delete db_abc123def456 --project proj_abc123def456 --confirm db_abc123def456 ``` #### npm ```bash npx prisma postgres delete db_abc123def456 --project proj_abc123def456 --confirm db_abc123def456 ``` Without `--confirm`, a non-interactive run stops with `CLI.CONSENT_REQUIRED` and tells you the exact token to pass. The delete itself did not complete while validating this guide (the platform returned a server error for every delete that day), so no success output is shown. ## 4. Add the GitHub Actions workflow [#4-add-the-github-actions-workflow] In this step you set up a GitHub Actions workflow that provisions a Prisma Postgres database when a pull request is opened, reopened, or updated, and deletes it when the pull request is closed. ### 4.1. Create the workflow file [#41-create-the-workflow-file] ```bash mkdir -p .github/workflows touch .github/workflows/prisma-postgres-preview.yml ``` The workflow: * Finds or creates a database named after the pull request * Applies the repository's migrations with `db migrate` and checks them with `db verify` * Seeds the database * Comments on the pull request * Deletes the database when the pull request is closed * Supports manual runs for both provisioning and cleanup > [!NOTE] > The workflow creates databases in `us-east-1`. Change `PRISMA_POSTGRES_REGION` in the `env` block to the [region](https://www.prisma.io/docs/postgres/npx-create-db#available-cli-options) closest to your runners. ### 4.2. Add the base configuration [#42-add-the-base-configuration] Paste the following into `.github/workflows/prisma-postgres-preview.yml`, which sets the triggers, the secrets the CLI reads, and the raw database name: ```yaml title=".github/workflows/prisma-postgres-preview.yml" name: Prisma Postgres preview database on: pull_request: types: [opened, reopened, synchronize, closed] workflow_dispatch: inputs: action: description: "Action to perform" required: true default: "provision" type: choice options: - provision - cleanup database_name: description: "Database name (optional, sanitized before use)" required: false type: string env: PRISMA_SERVICE_TOKEN: ${{ secrets.PRISMA_SERVICE_TOKEN }} PRISMA_WORKSPACE_ID: ${{ secrets.PRISMA_WORKSPACE_ID }} PRISMA_PROJECT_ID: ${{ secrets.PRISMA_PROJECT_ID }} PRISMA_POSTGRES_REGION: us-east-1 RAW_DB_NAME: ${{ github.event.pull_request.number != null && format('pr-{0}-{1}', github.event.pull_request.number, github.event.pull_request.head.ref) || (inputs.database_name != '' && inputs.database_name || format('test-{0}', github.run_number)) }} concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true ``` `PRISMA_SERVICE_TOKEN` and `PRISMA_WORKSPACE_ID` are how the CLI authenticates without a browser: with those two variables set, every platform command in the job runs as the service token. `PRISMA_PROJECT_ID` is passed to each command as `--project`, because the `postgres` commands do not pick the project up from the environment on their own. ### 4.3. Add the provision job [#43-add-the-provision-job] Append the following under a `jobs:` key. The job installs dependencies, finds or creates the database, applies migrations, seeds, and comments on the pull request: ```yaml title=".github/workflows/prisma-postgres-preview.yml" jobs: provision-database: if: (github.event_name == 'pull_request' && github.event.action != 'closed') || (github.event_name == 'workflow_dispatch' && inputs.action == 'provision') runs-on: ubuntu-latest permissions: contents: read pull-requests: write timeout-minutes: 15 steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: "24" cache: "npm" - name: Install dependencies run: npm ci - name: Validate secrets run: | for name in PRISMA_SERVICE_TOKEN PRISMA_WORKSPACE_ID PRISMA_PROJECT_ID; do if [ -z "${!name}" ]; then echo "Error: $name secret is not set" exit 1 fi done - name: Sanitize database name run: | DB_NAME="$(echo "$RAW_DB_NAME" | tr '/' '-' | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9-\n' '-' | cut -c1-63)" echo "DB_NAME=$DB_NAME" >> "$GITHUB_ENV" - name: Find existing database id: find-db run: | DB_ID="$(npx prisma postgres list --project "$PRISMA_PROJECT_ID" --json \ | jq -r --arg name "$DB_NAME" 'select(.kind == "result") | .envelope.result.items[] | select(.name == $name) | .id')" if [ -n "$DB_ID" ]; then echo "Database $DB_NAME exists with id $DB_ID" echo "db-id=$DB_ID" >> "$GITHUB_OUTPUT" else echo "No database named $DB_NAME yet" fi - name: Create database id: create-db if: steps.find-db.outputs.db-id == '' run: | RESULT="$(npx prisma postgres create "$DB_NAME" --project "$PRISMA_PROJECT_ID" --region "$PRISMA_POSTGRES_REGION" --json \ | jq -c 'select(.kind == "result") | .envelope')" if [ "$(echo "$RESULT" | jq -r '.ok')" != "true" ]; then echo "Failed to create database:" echo "$RESULT" | jq '.error' exit 1 fi DATABASE_URL="$(echo "$RESULT" | jq -r '.result.connectionString')" echo "::add-mask::$DATABASE_URL" echo "DATABASE_URL=$DATABASE_URL" >> "$GITHUB_ENV" echo "Created database $DB_NAME ($(echo "$RESULT" | jq -r '.result.database.id'))" - name: Create a connection for the existing database if: steps.find-db.outputs.db-id != '' run: | RESULT="$(npx prisma postgres connection create "${{ steps.find-db.outputs.db-id }}" --project "$PRISMA_PROJECT_ID" --json \ | jq -c 'select(.kind == "result") | .envelope')" if [ "$(echo "$RESULT" | jq -r '.ok')" != "true" ]; then echo "Failed to create a connection:" echo "$RESULT" | jq '.error' exit 1 fi DATABASE_URL="$(echo "$RESULT" | jq -r '.result.connectionString')" echo "::add-mask::$DATABASE_URL" echo "DATABASE_URL=$DATABASE_URL" >> "$GITHUB_ENV" - name: Apply migrations run: | npx prisma db migrate --db "$DATABASE_URL" npx prisma db verify --db "$DATABASE_URL" - name: Seed database run: npm run seed - name: Comment on the pull request if: success() && github.event_name == 'pull_request' uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: `Database provisioned successfully!\n\nDatabase name: ${process.env.DB_NAME}\nStatus: Ready and seeded with sample data` }) ``` Two details are worth knowing: * A database name is only known once; the connection string is not. When the pull request is reopened or gets a new commit, the database already exists, so the job asks for a fresh connection string with `postgres connection create` instead of creating a second database. * The connection string goes into `$GITHUB_ENV` as `DATABASE_URL` after `::add-mask::` hides it from the logs. The seed script and `src/prisma/db.ts` read `process.env.DATABASE_URL`, and the CLI steps pass it with `--db`, so no `.env` file is needed in CI. ### 4.4. Add the cleanup job [#44-add-the-cleanup-job] Append the cleanup job after `provision-database`, at the same indentation. It looks the database up by name and deletes it: ```yaml title=".github/workflows/prisma-postgres-preview.yml" cleanup-database: if: (github.event_name == 'pull_request' && github.event.action == 'closed') || (github.event_name == 'workflow_dispatch' && inputs.action == 'cleanup') runs-on: ubuntu-latest permissions: contents: read timeout-minutes: 5 steps: - name: Check out the repository uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: "24" cache: npm - name: Install dependencies run: npm ci - name: Validate secrets run: | for name in PRISMA_SERVICE_TOKEN PRISMA_WORKSPACE_ID PRISMA_PROJECT_ID; do if [ -z "${!name}" ]; then echo "Error: $name secret is not set" exit 1 fi done - name: Sanitize database name run: | DB_NAME="$(echo "$RAW_DB_NAME" | tr '/' '-' | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9-\n' '-' | cut -c1-63)" echo "DB_NAME=$DB_NAME" >> "$GITHUB_ENV" - name: Delete database run: | DB_ID="$(npx prisma postgres list --project "$PRISMA_PROJECT_ID" --json \ | jq -r --arg name "$DB_NAME" 'select(.kind == "result") | .envelope.result.items[] | select(.name == $name) | .id')" if [ -z "$DB_ID" ]; then echo "No database named $DB_NAME, nothing to delete" exit 0 fi echo "Deleting $DB_NAME ($DB_ID)" npx prisma postgres delete "$DB_ID" --project "$PRISMA_PROJECT_ID" --confirm "$DB_ID" ``` The cleanup job checks out the repository and installs dependencies too, so `npx prisma` runs the same CLI version as the provision job. ## 5. Store the code in GitHub [#5-store-the-code-in-github] If you do not have a repository yet, [create one on GitHub](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-new-repository). Then push the project, including `migrations/` and the workflow file: ```bash git init git add . git commit -m "Add Prisma 8 and the preview database workflow" git branch -M main git remote add origin https://github.com//.git git push -u origin main ``` `.env` and `.prisma/` are gitignored, so neither your development connection string nor your local project link is pushed. ## 6. Add the secrets in GitHub [#6-add-the-secrets-in-github] The workflow needs three secrets. ### 6.1. Service token [#61-service-token] A service token lets the CLI act on your workspace without a browser: 1. Open [Prisma Console](https://console.prisma.io/?utm_source=docs\&utm_medium=content\&utm_content=guides) and select the workspace that holds your `prisma-gha-preview` project. 2. Go to **Settings** and then **Service Tokens**. 3. Click **New Service Token**, then copy the token. It is shown once. Service tokens do not expire; revoke it from the same page when you no longer need it. ### 6.2. Workspace id and project id [#62-workspace-id-and-project-id] The CLI needs the workspace the token belongs to and the project to create databases in. Print both from your project directory: #### bun ```bash bunx prisma auth whoami --json bunx prisma project show --json ``` #### pnpm ```bash pnpm prisma auth whoami --json pnpm prisma project show --json ``` #### yarn ```bash yarn prisma auth whoami --json yarn prisma project show --json ``` #### npm ```bash npx prisma auth whoami --json npx prisma project show --json ``` Copy `workspace.id` from the first result and `project.id` (the `proj_...` value) from the second. ### 6.3. Add them to the repository [#63-add-them-to-the-repository] 1. Open your GitHub repository and go to **Settings**. 2. Expand **Secrets and variables** and click **Actions**. 3. Click **New repository secret** and add each of the following: * `PRISMA_SERVICE_TOKEN`: the service token * `PRISMA_WORKSPACE_ID`: the workspace id * `PRISMA_PROJECT_ID`: the project id The workflow reads them through the `env` block in step 4.2. ## 7. Try the workflow [#7-try-the-workflow] You can test the setup in two ways. The workflow run itself was not executed while validating this guide; the commands it runs were, in step 3. **Option 1: Open a pull request** 1. Create a branch, change something, and open a pull request. 2. The `provision-database` job creates a database named `pr--`, applies the migrations, seeds it, and comments on the pull request. 3. Push another commit and the job runs again against the same database with a fresh connection string. 4. Close the pull request and the `cleanup-database` job deletes the database. **Option 2: Run it manually** 1. Open the **Actions** tab and select **Prisma Postgres preview database**. 2. Click **Run workflow**, choose `provision`, and optionally enter a database name. 3. Run it again with `cleanup` and the same name to delete the database. ## 8. Migrate a shared database from a workflow [#migrate-a-shared-database] The preview job creates a new database per pull request. A database other people share, such as staging, is migrated by a job that runs on every push to `main`, and that job is one command: `db migrate`. It is safe to run unattended. Before any operation runs, it reads the database's marker and refuses with `MIGRATION.MARKER_MISMATCH` if that marker names a state that no migration in your files ends at, which is what a `db update` run against the database, or a migration from a branch that never merged, leaves behind. Each operation then checks that the database is in the state it expects before it runs, and a successful run writes the new marker. Three terms come up in this section, so here is what they mean. `contract emit` prints a `storageHash`, and that hash identifies one version of your contract. Prisma ORM calls that version a contract state. The database records which state it matches in a marker, a row Prisma ORM keeps in the database. A ref is a name you give a state, like a Git tag. The job applies migrations only up to the state a ref called `staging` points at, so a migration merged after that point waits until you point the ref at it. [Name important states with refs](https://www.prisma.io/docs/orm/migrations/the-migration-graph#name-important-states-with-refs) has the details. Create the `staging` ref in the pull request that adds the migration. `migration ref set` takes the migration directory's bare name, `20260910T1549_init` from the path `migration plan` printed in step 2.4. It points the ref at the state after that migration and writes `migrations/app/refs/staging.json`. Commit that file with the migration: #### bun ```bash bunx prisma migration ref set staging 20260910T1549_init ``` #### pnpm ```bash pnpm prisma migration ref set staging 20260910T1549_init ``` #### yarn ```bash yarn prisma migration ref set staging 20260910T1549_init ``` #### npm ```bash npx prisma migration ref set staging 20260910T1549_init ``` Do this again in every pull request that adds a migration: run `migration ref set staging ` and commit the updated `staging.json`. The job applies migrations only up to the state the ref names, so if you forget, the new migration never runs. On a repository that already has migrations, point the ref at the newest one staging should have. If `staging.json` does not exist at all, the job fails with `MIGRATION.REF_NOT_FOUND`. Add the connection string of your staging database, whether you created it with `postgres create` as in step 3 or host it elsewhere, as a repository secret named `STAGING_DATABASE_URL`, as in step 6.3. Then add the workflow. It needs no platform secrets, because it runs no `postgres` commands, and no `contract emit`, because the emitted contract is committed with the migrations: ```yaml title=".github/workflows/migrate-staging.yml" name: Migrate staging on: push: branches: [main] jobs: migrate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 24 cache: npm - run: npm ci - run: npx prisma db migrate --to staging --db "$DATABASE_URL" env: DATABASE_URL: ${{ secrets.STAGING_DATABASE_URL }} ``` When the job fails with `MIGRATION.MARKER_MISMATCH`, [Drift](https://www.prisma.io/docs/orm/migrations/rollbacks-and-recovery#drift-when-the-database-isnt-where-migrations-left-it) says what to do. To see what the job would apply before you push, run `npx prisma migration status --to staging --db "$STAGING_DATABASE_URL"` from your machine; it changes nothing. The workflow has no `db verify` step. `db verify` compares the database with the contract you last emitted, so if you point the `staging` ref at an earlier state on purpose, `db verify` would fail even though nothing is wrong. As in step 7, this workflow was not run on GitHub while validating the guide; its command was run against a local database, including one that had been changed outside the migration system, which failed with `MIGRATION.MARKER_MISMATCH`. ## Common gotchas [#common-gotchas] > [!WARNING] > Always pass `--project` to the `postgres` commands in CI. From a fresh checkout there is no `.prisma/local.json`, and the commands do not fall back to the `PRISMA_PROJECT_ID` environment variable; without the flag they stop with `PROJECT.SETUP_REQUIRED`. > [!WARNING] > Keep the `db.close()` at the end of the seed script: without it the connection pool keeps the Node.js process alive after the seed finishes, and the job runs until its timeout. > [!NOTE] > Use `--json` in every CI step that needs a value back. The `--json` stream ends with a `"kind": "result"` line; select that line with `jq 'select(.kind == "result")'` before reading `.envelope`, because progress events come first on the same stream. ## 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. `init` and `orm init` are different commands. `orm init` sets up Prisma ORM in a project, and you ran it in [step 2.1](#21-initialize-prisma-orm). `init` installs the agent skills and does not set up Prisma ORM. Prompts that map to this guide: * "Using the prisma-8 skill, add a `Comment` model to the contract, plan a migration for it, and update the seed script." * "Add a test job to the preview workflow that runs `npm test` against the provisioned database after the seed step." * "Change the workflow to create the preview database in `eu-central-1` and to skip the seed when the pull request is a draft." * "Add a `migrate-production.yml` workflow like the staging one in step 8 that runs on a published release, with a `production` ref and a `PRODUCTION_DATABASE_URL` secret." ## Next steps [#next-steps] You now have an automated GitHub Actions setup for ephemeral Prisma Postgres databases: one database per pull request, migrations applied and checked, sample data seeded, and cleanup on close. Extend it by running your test suite against the provisioned database, or by writing the connection string into a preview deployment. * [`postgres` command reference](https://www.prisma.io/docs/cli/postgres): connections, backups, usage, and regions. * [Applying a migration](https://www.prisma.io/docs/orm/migrations/applying-a-migration): what `db migrate` checks before and after each step. * [Writing data](https://www.prisma.io/docs/orm/fundamentals/writing-data): the create, update, and upsert calls the seed script uses. ## Related pages - [`AI SDK (with Next.js)`](https://www.prisma.io/docs/guides/integrations/ai-sdk): Build a chat application with AI SDK, Prisma ORM, and Next.js that stores chat sessions and messages in Prisma Postgres. - [`Datadog`](https://www.prisma.io/docs/guides/integrations/datadog): Learn how to configure Datadog tracing for a Prisma ORM project. Capture spans for every query using the @prisma/instrumentation package, dd-trace, and view them in Datadog - [`Embedded Prisma Studio (with Next.js)`](https://www.prisma.io/docs/guides/integrations/embed-studio): Learn how to embed Prisma Studio directly in your Next.js application for database management - [`Permit.io`](https://www.prisma.io/docs/guides/integrations/permit-io): Learn how to implement access control with Prisma ORM with Permit.io - [`pgfence`](https://www.prisma.io/docs/guides/integrations/pgfence): Analyze Prisma Migrate SQL files for dangerous lock patterns, risk levels, and safe rewrite recipes before deploying to production