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.

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

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:

// 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/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) and point Prisma at its database:

# .env
DATABASE_URL=postgresql://postgres:[email protected]:54322/postgres
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:

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

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:

ssh -N -L 5432:127.0.0.1:5432 [email protected]
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:

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:

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.

Updated 11 November 2026 · View as Markdown · Something wrong or missing? Tell us