# Prisma

> How to use Prisma with SupaHost. Prisma needs a direct Postgres connection, which SupaHost offers only through the Business SSH tunnel, so use it for schema and migrations and supabase-js in your app.

Source: https://supahost.app/docs/integrations/prisma · Updated 2026-11-11

Prisma connects straight to Postgres over the Postgres wire protocol. On SupaHost, Postgres isn't exposed to the internet. The only direct connection is the restricted SSH tunnel on Business and Enterprise, which is opened from a machine you control, such as your laptop.

That decides how Prisma fits:

| You want to | Plan | How |
|---|---|---|
| Design the schema in `schema.prisma` and generate migrations | Any | Run Prisma against the Supabase CLI's local database, then apply the SQL to SupaHost |
| Run `prisma migrate deploy` against SupaHost | Business, Enterprise | Through the SSH tunnel from a dev machine or CI runner |
| Query from your deployed app with Prisma Client | Not supported | Use supabase-js (or another Data API client) in the app |

If your app needs an ORM with a live database connection in production, SupaHost isn't a good fit today. If Prisma is mainly how you manage the schema, it works well.

## Setup

```bash
npm install prisma --save-dev
npm install @prisma/client @prisma/adapter-pg pg dotenv
npx prisma init
```

Recent Prisma versions keep the connection URL in `prisma.config.ts`:

```ts
// prisma.config.ts
import 'dotenv/config'
import { defineConfig, env } from 'prisma/config'

export default defineConfig({
  schema: 'prisma/schema.prisma',
  migrations: { path: 'prisma/migrations' },
  datasource: { url: env('DATABASE_URL') },
})
```

```prisma
// prisma/schema.prisma
generator client {
  provider = "prisma-client"
  output   = "../generated/prisma"
}

datasource db {
  provider = "postgresql"
}

model Invoice {
  id         BigInt   @id @default(autoincrement())
  customerId String   @map("customer_id") @db.Uuid
  amountCents Int     @map("amount_cents")
  status     String   @default("draft")
  createdAt  DateTime @default(now()) @map("created_at") @db.Timestamptz

  @@map("invoices")
}
```

`@@map` and `@map` keep table and column names in snake_case, which is what the Data API and supabase-js expect.

## Generate migrations locally (any plan)

Start the local stack with `npx supabase start` (see [Local development](/docs/local-development/overview)) and point Prisma at its database:

```bash
# .env
DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres
```

```bash
npx prisma migrate dev --name add_invoices
```

Prisma applies the change locally and writes `prisma/migrations/<timestamp>_add_invoices/migration.sql`. Prisma doesn't know about Row Level Security, so add it to the migration file before you ship it:

```sql
-- appended to migration.sql
alter table public.invoices enable row level security;

grant select on public.invoices to authenticated;

create policy "Customers read their own invoices"
  on public.invoices for select
  to authenticated
  using ((select auth.uid()) = customer_id);
```

Then apply the same SQL file to your SupaHost project with MCP `apply_migration` or Studio's SQL editor, as described in [Migrations workflow](/docs/local-development/migrations-workflow).

## Deploy migrations over the tunnel (Business)

Add your SSH key under **Connect → SSH & CLI**, open the tunnel, and use the connection string from **Show connection string**:

```bash
ssh -N -L 5432:127.0.0.1:5432 supahost@acme.supahost.space
```

```bash
DATABASE_URL='postgresql://postgres:<password>@127.0.0.1:5432/p_app_one' \
  npx prisma migrate deploy
```

`migrate deploy` applies pending migrations and records them in Prisma's own `_prisma_migrations` table. Use `deploy`, not `dev`, against SupaHost: `migrate dev` needs a shadow database and may offer to reset the schema.

If the project already has tables, introspect them first with `npx prisma db pull`, create a baseline migration, and mark it as applied with `npx prisma migrate resolve --applied <name>` so Prisma doesn't try to create those tables again.

## Querying in your app

Your deployed app can't reach the tunnel, so use supabase-js for queries. It runs as the signed-in user, so the RLS policy above applies:

```ts
const { data: invoices, error } = await supabase
  .from('invoices')
  .select('id, amount_cents, status, created_at')
  .order('created_at', { ascending: false })
```

Prisma Client is still handy in scripts you run through the tunnel on Business, such as a one-off backfill:

```ts
import 'dotenv/config'
import { PrismaPg } from '@prisma/adapter-pg'
import { PrismaClient } from './generated/prisma/client'

const prisma = new PrismaClient({
  adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }),
})

await prisma.invoice.updateMany({ where: { status: 'draft' }, data: { status: 'open' } })
await prisma.$disconnect()
```

These connections log in as the `postgres` role, not as one of your app's users. Policies written for `authenticated` don't apply to them, so be careful with writes.

## Things to keep out of Prisma

- **The `auth` and `storage` schemas.** They are managed by Supabase services. Reference `auth.users` ids as plain `uuid` columns rather than modelling the auth tables.
- **RLS, grants and database functions.** Write them as SQL in your migrations.
- **Prisma Accelerate or Data Proxy connection strings** pointing at SupaHost. They need to reach your database over the internet, which SupaHost doesn't allow.

## Related

- [Common Prisma errors with Supabase](/kb/prisma-errors)
- [Can I use Drizzle or Prisma without supabase-js?](/kb/orm-without-supabase-js)
- [SSH access and direct Postgres](/docs/platform/ssh)
- [Drizzle ORM](/docs/integrations/drizzle)
