Loading...
Loading...
Use Prisma Next with a Supabase project via `@prisma-next/extension-supabase` — wire `extensions: [supabasePack]`, declare cross-space FKs to `supabase:auth.AuthUser`, author RLS policies (`policy_select` / `policy_update` / `@@rls`, `auth.uid()` predicates), build `db.ts` with the `supabase()` factory, bind roles per request (`asUser(jwt)` / `asAnon()` / `asServiceRole()`), query `auth.*` / `storage.*` via the `db.asServiceRole().supabase` admin root, and validate JWTs (`jwksUrl` for current projects / `jwtSecret` for legacy HS256). Use for supabase, RLS, row level security, policy, role binding, anon, authenticated, service_role, auth.users, auth.uid(), JWT, JWKS, SUPABASE_JWKS_URL, SUPABASE_JWT_SECRET, SUPABASE.JWT_INVALID, SUPABASE.CONFIG_INVALID, RoleBoundDb, session pooler, supabase:auth.AuthUser, @prisma-next/extension-supabase.
npx skill4agent add prisma/prisma-next prisma-next-supabaseEdit your data contract. Prisma handles the rest.
supabase()policy_select@@rlsauth.uid()asUser(jwt)asAnon()asServiceRole()auth.usersauth.*storage.*prisma-next-contractdb.tsprisma-next-runtimeprisma-next-queriesdbprisma-next-migrationsexternal@prisma-next/extension-supabase/packauthstorageanonauthenticatedservice_roleexternalextensionsdb verifymanagedroles = [authenticated]not-foundsupabase()SupabaseDbdb.sqldb.ormawait db.asUser(jwt)db.asAnon()db.asServiceRole()RoleBoundDb.sql.orm.raw.execute(plan).transaction(fn)set_config('role', …)set_config('request.jwt.claims', …)RESET ALLauth.uid()auth.jwt()GRANTGRANTpublicservice_roleauth.*storage.*jwksUrlasUser(jwt)josejwksUrljwtSecretSUPABASE.CONFIG_INVALIDSUPABASE.JWT_INVALIDmeta.reasonjwtSecretjwksUrlroleauthenticatedsupabase statusJWT_SECRETauth.*storage.*service_roledb.asServiceRole().supabase.sql.orm.nativeEnums.executeservice_roleservice_roleauth.*storage.*USAGEpostgresasUserasAnon.supabaseasServiceRole().sql.orm/controldefineConfig({ extensions: [...] })extensionsexamples/supabase/prisma-next.config.ts// prisma-next.config.ts
import postgresAdapter from '@prisma-next/adapter-postgres/control';
import { defineConfig } from '@prisma-next/cli/config-types';
import postgresDriver from '@prisma-next/driver-postgres/control';
import supabasePack from '@prisma-next/extension-supabase/pack';
import sql from '@prisma-next/family-sql/control';
import { prismaContract } from '@prisma-next/sql-contract-psl/provider';
import postgres from '@prisma-next/target-postgres/control';
import postgresPackRef from '@prisma-next/target-postgres/pack';
import { postgresCreateNamespace } from '@prisma-next/target-postgres/types';
export default defineConfig({
family: sql,
target: postgres,
adapter: postgresAdapter,
driver: postgresDriver,
extensions: [supabasePack],
contract: prismaContract('./src/contract.prisma', {
output: 'src/contract.json',
target: postgresPackRef,
createNamespace: postgresCreateNamespace,
}),
migrations: { dir: 'migrations' },
});auth.userspublicauthstoragesupabase:auth.AuthUserREFERENCES "auth"."users"("id")policy_<operation>@@rlsexamples/supabase/src/contract.prismatypes {
Uuid = String @db.Uuid
}
namespace public {
model Profile {
id Uuid @id @default(uuid())
username String
userId Uuid @unique
user supabase:auth.AuthUser @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("profile")
@@rls
}
// authenticated may read only their own profile.
policy_select profile_owner_read {
target = Profile
roles = [authenticated]
using = "\"userId\"::uuid = auth.uid()"
}
// anon may read every profile (a public directory listing).
policy_select profile_public_read {
target = Profile
roles = [anon]
using = "true"
}
// authenticated may update only their own profile, and may not
// reassign it to another owner (WITH CHECK).
policy_update profile_owner_write {
target = Profile
roles = [authenticated]
using = "\"userId\"::uuid = auth.uid()"
withCheck = "\"userId\"::uuid = auth.uid()"
}
}policy_selectpolicy_insertpolicy_updatepolicy_deletepolicy_allkey = valuetargetrolesanonauthenticatedservice_roleusingwithCheck(target, operation)@@rlspolicy_*@@rlsPSL_EXTENSION_TARGET_MODEL_MISSING_ATTRIBUTE@@rls\"userId\"auth.uid()uuid@prisma-next/postgres/contract-builderpolicySelectpolicyInsertpolicyUpdatepolicyDeletepolicyAllrlsEnabled(Model)role('anon')prisma-next contract emitprisma-next-migrationsENABLE ROW LEVEL SECURITYCREATE POLICYauth.*db.tssupabase()postgres()supabase()/runtimejwksUrl// src/prisma/db.ts
import { supabase } from '@prisma-next/extension-supabase/runtime';
import type { Contract } from './contract.d';
import contractJson from './contract.json' with { type: 'json' };
export const db = await supabase<Contract>({
contractJson,
url: process.env['DATABASE_URL'], // direct Postgres connection — see pitfalls
jwksUrl: process.env['SUPABASE_JWKS_URL'], // https://<project-ref>.supabase.co/auth/v1/.well-known/jwks.json
// Legacy HS256 projects use jwtSecret: process.env['SUPABASE_JWT_SECRET'] instead — exactly one of the two.
});middlewarepostgres()prisma-next-runtimeset_configpoolOptionspgpg.Poolpg.Clienturlawait db.close()await usingprisma-next-runtimeRoleBoundDbprisma-next-queries// A signed-in user: rows are RLS-scoped to the JWT's auth.uid().
const userDb = await db.asUser(jwt); // async — rejects with code SUPABASE.JWT_INVALID on a bad/expired token
const mine = await userDb.orm.public.Profile.select('id', 'username').all();
// The anon role: sees what anon policies permit.
const listing = await db.asAnon().orm.public.Profile.select('id', 'username').all();
// service_role: BYPASSRLS — sees everything in YOUR contract.
const all = await db.asServiceRole().orm.public.Profile.select('id', 'username').all();
// Writes ride the same surfaces; RLS filters them too. An UPDATE against
// another owner's row affects 0 rows; a withCheck violation raises an error.
const updated = await userDb.orm.public.Profile
.where({ userId: me })
.updateAndCount({ username: 'new-name' });asAnon()asServiceRole()asUserorm.public.Profilesql.public.profileprisma-next-queriesRoleBoundDb.transaction(fn)auth.*storage.*service_roledb.asServiceRole().supabaseconst admin = db.asServiceRole();
// SQL builder over the pack contract:
const users = await admin.supabase
.execute(admin.supabase.sql.auth.users.select('id', 'email').build())
.toArray();
// ORM over the pack contract:
const sessions = await admin.supabase.orm.auth.AuthSession.select('id', 'aal').all();
// Native enum values (e.g. auth.aal_level) come typed:
type AalLevel = (typeof admin.supabase.nativeEnums.auth.AalLevel)['Value'];service_roleauth.*storage.*permission denied for table users42501GRANT USAGE ON SCHEMA auth TO service_role;
GRANT SELECT ON TABLE auth.users TO service_role;asUserasAnon.supabase.transactionservice_roleGRANTpermission deniedpublicALTER DEFAULT PRIVILEGESpublicprisma-next db initmigrateanonauthenticatedservice_roleservice_roleauth.*storage.*GRANT USAGEGRANT SELECTpsqlpermission denied for table …42501set_configRESET ALLaws-0-<region>.pooler.supabase.com:5432postgres.<project-ref>db.<project-ref>.supabase.co:5432.envDATABASE_URLSUPABASE_JWKS_URLhttps://<project-ref>.supabase.co/auth/v1/.well-known/jwks.jsonhttp://127.0.0.1:54321/auth/v1/.well-known/jwks.jsonSUPABASE_JWT_SECRETsupabase statusJWT_SECRETalgjwtSecretsupabase statusJWT_SECRETasUserSUPABASE.JWT_INVALIDjwksUrlSUPABASE_JWKS_URLjwtSecretpublicauth.*service_rolepermission denieddb.sqldb.ormdbRoleBoundDbawaitsupabase()asUser(jwt)asAnon()asServiceRole().supabaseasUserasAnonauth.*service_roleservice_rolepolicy_*@@rlsPSL_EXTENSION_TARGET_MODEL_MISSING_ATTRIBUTE@@rls"userId"auth.uid()::uuiduuidjwksUrljwtSecretsupabase()SUPABASE.CONFIG_INVALIDawait.catchUPDATEwithCheck/controldefineConfig({ extensions: [...] })extensionsprisma-next-feedbackGRANTservice_roleauth.*psqlprisma-next-feedback.supabaseauth.usersauth.uid()@supabase/supabase-jsexamples/supabasedb.tspackages/3-extensions/supabase/README.mdpackages/3-extensions/supabase/src/runtime/supabase.tsSupabaseOptionsRoleBoundDbServiceRoleDbextensions: [supabasePack]defineConfig/controlsupabase:auth.AuthUserfieldsreferencesonDelete@@rlsauth.uid()db.tsawait supabase<Contract>({ contractJson, url, jwksUrl | jwtSecret })jwksUrljwtSecretRoleBoundDbasUserasAnonasServiceRoleasUserauth.*storage.*asServiceRole().supabaseGRANT USAGE ON SCHEMA authGRANT SELECTpublic/controldb.sql.supabase