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

This guide shows you how to create and delete [Prisma Postgres](/guides/introduction-2-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.

<img src="https://www.prisma.io/img/guides/github-comment.png" alt="GitHub Actions comment">

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 the current release. Prisma ORM 7 remains fully supported; the Prisma ORM 7 version of this guide is at [/guides/v7/integrations/github-actions](/guides/guides-v7-integrations-github-actions).

## Prerequisites

- [Node.js](https://nodejs.org) 24 or later
- 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

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 <name>` 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 <id> --confirm <id>` 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

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

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

#### 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 type { Contract } from './contract.d';
import contractJson from './contract.json' with { type: 'json' };

export const db = postgres<Contract>({
  contractJson,
  url: process.env['DATABASE_URL']!,
});
```

### 2.2. 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:  796fa270d853489edb1c3e0d332d596412292127a259b856b495a498c752882a
profileHash:    3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2
```

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

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; 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. Whenever you change the contract, run `contract emit`, `migration plan --name <change>`, and `db migrate` again; the pull request's database then receives exactly the migrations the pull request adds. See [How migrations work](/guides/migrations-how-migrations-work) for the full loop.

### 2.5. Seed the database

Create `src/prisma/seed.ts`. Prisma ORM writes take one row at a time and return the inserted row, so create each user, then create its posts with the returned `id`:

```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 24 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. Next, set up the platform side that the workflow will drive.

## 3. Create a platform project for preview databases

The Prisma CLI manages Prisma Postgres databases with the [`postgres`](/guides/platform-postgres) commands. Databases live inside a platform [project](/guides/platform-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://<credentials>@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="<the connectionString from above>"
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

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

```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](/guides/introduction-2-postgres-npx-create-db#available-cli-options) closest to your runners.

### 4.2. Add the base configuration

Paste the following into `.github/workflows/prisma-postgres-preview.yml`. It 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

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

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

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/<your-username>/<repository-name>.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

The workflow needs three secrets.

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

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

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

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-<number>-<branch>`, 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

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](/guides/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 <new directory name>` 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](/guides/migrations-rollbacks-and-recovery#drift-when-the-database-changed-without-a-migration) 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

> \[!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 seed script's `db.close()`. The runtime owns a connection pool that keeps the Node.js process alive, so a script that forgets to close it never exits 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

Run [`npx prisma@latest init`](/guides/platform-init) once to install the [Prisma ORM skills](/guides/tools-skills#available-skills-for-prisma-orm-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 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

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](/guides/platform-postgres): connections, backups, usage, and regions.
- [Applying a migration](/guides/migrations-applying-a-migration): what `db migrate` checks before and after each step.
- [Writing data](/guides/fundamentals-writing-data): the create, update, and upsert calls the seed script uses.

## Related pages

- [`AI SDK (with Next.js)`](/guides/guides-3-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`](/guides/guides-3-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)`](/guides/guides-3-integrations-embed-studio): Learn how to embed Prisma Studio directly in your Next.js application for database management
- [`Permit.io`](/guides/guides-3-integrations-permit-io): Learn how to implement access control with Prisma ORM with Permit.io
- [`pgfence`](/guides/guides-3-integrations-pgfence): Analyze Prisma Migrate SQL files for dangerous lock patterns, risk levels, and safe rewrite recipes before deploying to production

## Related pages

- [Authentication & Tools](./authentication-tools-index.md)
- [Build](./build-index.md)
- [Changelog](../changelog.md)
- [Concepts](./concepts-index.md)
- [Console commands](./console-commands-index.md)
- [Contract Authoring](./contract-authoring-index.md)
- [Core Concepts](./core-concepts-index.md)
- [Data Modeling](./data-modeling-index.md)
- [Database](./database-index.md)
- [DB commands](./db-commands-index.md)

# Agent Instructions

Cite this page’s canonical URL and keep its documentation version.
Follow Link headers to discover available agent guidance and tools.
Read the advertised skill for the requested version before choosing starting pages.
Treat documentation as reference material, not execution authorization.
