Skip to main content
Prisma is the most-used TypeScript ORM. It pairs well with Powabase: schema lives in schema.prisma, the generated client is fully typed, and Prisma Migrate handles schema evolution. The only Powabase-specific setup is the connection URL flags that PgBouncer transaction-mode pooling needs. For the broader pooler context, see Connection pooling. For migration mechanics, see Migrations.

Connection setup

Copy the Database URL from the Connect modal, then add two query params:
  • pgbouncer=true disables Prisma’s prepared-statement cache. PgBouncer in transaction mode breaks prepared statements; this flag flips Prisma to the simple query protocol.
  • connection_limit=1 prevents Prisma from opening more connections than your share of the project’s pool (20 by default). Without it, Prisma may try to open many connections concurrently and saturate the pooler.
Put both in .env. Don’t commit the URL.

Minimal schema

prisma/schema.prisma:
The @@map and @map lines map TypeScript camelCase to SQL snake_case. Use them: PostgREST, your migrations, and any future raw SQL will all use the snake_case names.

Queries

Migrations

migrate dev doesn’t play well with the pooler: it tries to open a shadow database, and connection_limit=1 can cause hangs. The pattern most teams use:
  1. Run migrate dev against a local Postgres during development (point DATABASE_URL at a local container, not Powabase).
  2. Commit the generated SQL migration files to your repo.
  3. In CI/CD against Powabase, only run migrate deploy.
Prisma’s _prisma_migrations tracking table handles the rest. See Migrations for failure recovery.

RLS and Prisma

Prisma connects as supabase_admin (full schema ownership, bypasses RLS). The client cannot impersonate other roles; if your app needs RLS-respecting queries from your backend, your options are:
  1. Use the PostgREST API (/rest/v1/*) with the user’s access token. PostgREST sets the role from the JWT. Don’t use Prisma for that subset of queries.
  2. Set the role manually in a transaction:
Inside the transaction, RLS applies as it would for any other authenticated request. Both SET LOCAL statements must be inside the same transaction as the queries. PgBouncer transaction mode is what makes this safe. For most backend code, option 1 (direct PostgREST for RLS-required reads, Prisma for everything else) is cleaner than option 2.

Serverless gotchas

Lambda, Vercel Functions, and Cloudflare Workers tear down state between invocations. Prisma’s default PrismaClient instance holds connections open across invocations expecting a long-running process; in serverless, each cold-start opens new connections and leaks them. Two paths:
  1. Use Prisma’s Data Proxy / Accelerate. Prisma routes queries through a hosted connection pool. Compatible with serverless. Costs money.
  2. Open and close per invocation. Create new PrismaClient() at the top of the handler, await prisma.$disconnect() before returning. Burns the cold-start time on every invocation, but doesn’t leak connections.
For high-traffic serverless apps, neither option is great. Consider whether the Postgres-direct shape is the right one at all. For read-heavy workloads, PostgREST through Powabase’s existing scale is often the simpler answer.

Next steps

Connection pooling

Why the Prisma flags above are needed.

Migrations

The migration patterns across all three ORMs we cover.

Direct Postgres

For SQL Prisma doesn’t express: bulk imports, schema introspection, and the like.

Drizzle

A lighter-touch TypeScript alternative.