# 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 the current release. 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) 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 [#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@latest contract emit`, then `npx prisma@latest migration plan --name init` and `npx prisma@latest 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@latest auth whoami`; if I am not signed in, stop and ask me to run `npx prisma@latest auth login`. Create a dedicated platform project with `npx prisma@latest 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@latest 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 [#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 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 [#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@latest contract emit
```

#### pnpm

```bash
pnpm dlx prisma@latest contract emit
```

#### yarn

```bash
yarn dlx prisma@latest contract emit
```

#### npm

```bash
npx prisma@latest contract emit
```

```text no-copy
✔ Emitted contract.json and contract.d.ts

storageHash:    be6f22ab71a1817feececf0649ec929885e503b612c712bb113d4b5012109a73
executionHash:  4abff323cc88151ef9c9a0ec90122cfee6d46814a118cdb66a9fdd94a4123463
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@latest migration plan --name init
```

#### pnpm

```bash
pnpm dlx prisma@latest migration plan --name init
```

#### yarn

```bash
yarn dlx prisma@latest migration plan --name init
```

#### npm

```bash
npx prisma@latest 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:         be6f22ab71a1817feececf0649ec929885e503b612c712bb113d4b5012109a73
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@latest db migrate
```

#### pnpm

```bash
pnpm dlx prisma@latest db migrate
```

#### yarn

```bash
yarn dlx prisma@latest db migrate
```

#### npm

```bash
npx prisma@latest 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 be6f22ab71a1817feececf0649ec929885e503b612c712bb113d4b5012109a73
```

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](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`. 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 [#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@latest auth login
```

#### pnpm

```bash
pnpm dlx prisma@latest auth login
```

#### yarn

```bash
yarn dlx prisma@latest auth login
```

#### npm

```bash
npx prisma@latest auth login
```

Create the project from your project directory:

  

#### bun

```bash
bunx prisma@latest project create prisma-gha-preview
```

#### pnpm

```bash
pnpm dlx prisma@latest project create prisma-gha-preview
```

#### yarn

```bash
yarn dlx prisma@latest project create prisma-gha-preview
```

#### npm

```bash
npx prisma@latest 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@latest project show
```

#### pnpm

```bash
pnpm dlx prisma@latest project show
```

#### yarn

```bash
yarn dlx prisma@latest project show
```

#### npm

```bash
npx prisma@latest 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@latest postgres create pr-test --project proj_abc123def456 --json
```

#### pnpm

```bash
pnpm dlx prisma@latest postgres create pr-test --project proj_abc123def456 --json
```

#### yarn

```bash
yarn dlx prisma@latest postgres create pr-test --project proj_abc123def456 --json
```

#### npm

```bash
npx prisma@latest 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@latest db migrate --db "$PREVIEW_URL"
npx prisma@latest 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@latest postgres list --project proj_abc123def456
```

#### pnpm

```bash
pnpm dlx prisma@latest postgres list --project proj_abc123def456
```

#### yarn

```bash
yarn dlx prisma@latest postgres list --project proj_abc123def456
```

#### npm

```bash
npx prisma@latest 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@latest postgres delete db_abc123def456 --project proj_abc123def456 --confirm db_abc123def456
```

#### pnpm

```bash
pnpm dlx prisma@latest postgres delete db_abc123def456 --project proj_abc123def456 --confirm db_abc123def456
```

#### yarn

```bash
yarn dlx prisma@latest postgres delete db_abc123def456 --project proj_abc123def456 --confirm db_abc123def456
```

#### npm

```bash
npx prisma@latest 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`. 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 [#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@latest 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@latest 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@latest 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@latest db migrate --db "$DATABASE_URL"
          npx prisma@latest 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: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "24"

      - 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@latest 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@latest postgres delete "$DB_ID" --project "$PRISMA_PROJECT_ID" --confirm "$DB_ID"
```

The cleanup job does not check out the repository or install dependencies; it only needs Node.js to run `npx prisma@latest`.

## 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/<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 [#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@latest auth whoami --json
bunx prisma@latest project show --json
```

#### pnpm

```bash
pnpm dlx prisma@latest auth whoami --json
pnpm dlx prisma@latest project show --json
```

#### yarn

```bash
yarn dlx prisma@latest auth whoami --json
yarn dlx prisma@latest project show --json
```

#### npm

```bash
npx prisma@latest auth whoami --json
npx prisma@latest 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-<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.

## 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 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 [#prompt-your-coding-agent]

Run [`npx prisma@latest init`](https://www.prisma.io/docs/cli/init) once to install the [Prisma ORM skills](https://www.prisma.io/docs/ai/tools/skills#available-skills-for-prisma-8) for your coding agent and keep them matching your installed packages. Prompts that map to this guide:

* "Using the prisma-8 skill, add a `Comment` model to 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."

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