Prisma IDB FaviconPrisma IDB

Schema & Config

Write PSL and point Prisma Next at the IDB target

Prisma Next IDB reads PSL directly. No generator block and no Prisma Client needed.

Schema

model User {
  id    String  @id
  name  String
  email String? @unique
  todos Todo[]
}

model Todo {
  id     String  @id
  title  String
  done   Boolean
  userId String
  user   User    @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@index([userId], name: "byUser")
}

Supported scalar types: String, Int, Float, Boolean, DateTime, BigInt, Decimal, Json, Bytes.

IDB rules

  • Each model needs one scalar @id field or a single-field @@id([field]).
  • Compound primary keys are not supported.
  • Compound indexes are not supported.
  • Relation fields must declare @relation(fields: [...], references: [...]) on the foreign-key side.
  • Use @@map("storeName") to control the IndexedDB object store name. The store name is also the key under db.orm.
model Todo {
  id    String @id
  title String

  @@map("todos")
}
const todos = await db.orm.todos.all();

Client-only fields and models

If you sync a schema to a server (see Sync), some fields or models should never reach the browser: password hashes, session tokens, internal audit tables. Mark them with the IDB-family PSL attributes @idb.exclude (field) and @@idb.exclude (model):

model User {
  id       String @id
  name     String
  password String @idb.exclude // dropped from the client contract
  boards   Board[]
  sessions Session[]
}

model Session {
  id     String @id
  userId String
  user   User   @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@idb.exclude // whole model dropped from the client contract
}

Your IDB config (prismaIdbContract(path, { projection: "client" })) strips these before the browser ever sees them. That's the only projection flag you touch on the client side — "full" is just the interpreter's default (nothing stripped), not a second thing you feed to a server config.

If your server is also on the IDB family, you'd point it at the same schema without the projection option. But the realistic case — and what the sync server setup demonstrates — is a SQL/Postgres server, which can't parse @idb.exclude at all (its parser hard-errors on the unrecognized namespace). For that you don't touch projection here — see Client Contracts for the writeSqlSchemaWithSync helper that handles it, and what happens to a relation that points at an excluded model.

A few rules the interpreter enforces:

  • The @id field cannot be @idb.excluded.
  • An index cannot reference an excluded field.
  • An excluded field cannot also be @unique.

Config

import { defineConfig } from "@prisma-next-idb/family-idb/config-types";
import { prismaIdbContract } from "@prisma-next-idb/family-idb/contract-psl";
import idbFamily from "@prisma-next-idb/family-idb/control";
import idbTarget from "@prisma-next-idb/target-idb/control";
import idbAdapter from "@prisma-next-idb/adapter-idb/control";
import idbDriver from "@prisma-next-idb/driver-idb/control";

export default defineConfig({
  family: idbFamily,
  target: idbTarget,
  adapter: idbAdapter,
  driver: idbDriver,
  db: { connection: ":memory:" },
  contract: prismaIdbContract("src/prisma/schema.prisma"),
  migrations: { dir: "migrations" },
});

connection is present because the Prisma Next framework requires it. The IDB target does not use it. The prismaIdbContract path is the source schema; contract emit reads it and writes contract.json and contract.d.ts alongside it.

On this page