原始内容
name: Prisma slug: prisma version: 1.0.2 description: >- Designs Prisma schemas, writes type-safe queries, and fixes migrations, connection pools, and N+1 relation loads in Node and TypeScript. Use when editing schema.prisma, modeling relations, indexes, enums, JSON or Decimal columns, or choosing between db push, migrate dev and migrate deploy; when a migration drifts, fails, or would drop a column on rename, or a database needs baselining; on errors P2002, P2025, P2024, P2034 or P3009; when connections run out on Lambda, Vercel or behind PgBouncer; when queries are slow or include loads too much; on transaction timeouts, deadlocks and optimistic locking; when Decimal or BigInt break JSON.stringify; for $queryRaw and TypedSQL; when porting $use middleware to client extensions; when prisma generate fails in Docker, Alpine or CI; and for seeding and test isolation. Not for database-server tuning or hand-written SQL (pg, sql), or other ORMs. homepage: https://clawic.com/skills/prisma changelog: "Display name shown correctly" metadata: clawdbot: emoji: 🔺 requires: bins: - npx os: - linux - darwin - win32 displayName: Prisma configPaths: - ~/Clawic/data/prisma/
User preferences and memory live in ~/Clawic/data/prisma/ (see setup.md on first use, memory-template.md for the file format). If you have data at an old location (~/prisma/ or ~/clawic/prisma/), move it to ~/Clawic/data/prisma/.
When To Use
- Writing or reviewing
schema.prisma: models, relations, referential actions, indexes, enums, JSON, money and time columns - Getting a schema change into a database safely:
db pushvsmigrate devvsmigrate deploy, renames, backfills, baselining, drift, failed migrations - Writing Prisma Client queries: filters, pagination, nested writes, upserts, aggregation, and the raw escape hatch
- Diagnosing runtime failures by error code (P1xxx connectivity, P2xxx query, P3xxx migrate) or by symptom (slow, too many queries, pool exhausted, transaction timeout)
- Deploying Prisma: generate in CI and Docker, engine targets, serverless and edge, poolers, test databases
- Extending the client: soft delete, audit logs, tenant scoping, computed fields — and porting existing
$usemiddleware - Not for tuning the database server itself (
EXPLAINplans, vacuum, index internals) or hand-written SQL optimization — see Related Skills
Quick Reference
| Situation | Play |
|---|---|
| Schema edited, database unchanged | Dev: npx prisma migrate dev; prod/CI: migrate deploy; throwaway prototype only: db push (→ Core Rules 1) |
| "Unknown argument" or a model missing on the client | Client is stale generated code — npx prisma generate (→ Core Rules 2) |
| Renaming a field or table with rows in it | Prisma diffs by name: rename = DROP + ADD. @map to keep the column, or hand-edit the SQL (→ Core Rules 3) |
Existing database, no prisma/migrations folder |
db pull to introspect, then baseline the first migration as applied (→ migrations.md) |
"Drift detected" or migrate dev wants to reset prod-like data |
Someone changed the DB out of band; diff before you accept anything (→ migrations.md) |
| P3009 — failed migration blocks every deploy | Fix the SQL by hand, then migrate resolve --applied or --rolled-back (→ errors.md) |
| P2002 unique constraint failed | Genuine duplicate or an upsert race — catch the code, retry once (→ errors.md) |
| P2025 record not found on update/delete | Row gone, or an extra where filter did not match — this is the optimistic-locking signal (→ transactions.md) |
| P2024 timed out fetching a new connection | Pool exhausted: long transactions, too many client instances, or a limit below concurrency (→ connections.md) |
| "Too many connections" on Lambda, Vercel or Next.js dev | One client per process behind a globalThis singleton, plus an external pooler (→ connections.md) |
| Query log shows hundreds of queries per request | Relation loads in a loop, not include — measure before rewriting (→ performance.md) |
Relation is undefined at runtime |
Prisma never loads relations implicitly; include or select it (→ queries.md) |
| List endpoint gets slower as the table grows | take missing, offset pagination, or count() scanning (→ performance.md) |
| Transaction times out at 5s or deadlocks under load | Shrink the body, raise timeout deliberately, retry P2034 (→ transactions.md) |
| Soft delete, audit trail, tenant scoping, computed fields | Client extensions ($extends), not middleware (→ extensions.md) |
include result does not narrow in TypeScript |
GetPayload / validator instead of hand-written interfaces (→ typescript.md) |
Do not know how to serialize a BigInt, or Decimal arrives as an object |
Prisma returns BigInt and Decimal, not numbers (→ typescript.md) |
| Query the schema cannot express (CTE, window, DISTINCT ON, upsert-heavy batch) | $queryRaw tagged template, or TypedSQL for typed results (→ raw-sql.md) |
| Deploy fails with "Query engine could not be located" | prisma generate missing from the build, or wrong binaryTargets for the image (→ deployment.md) |
| Tests interfere with each other or need a real database | Per-worker database or schema, rollback-per-test, deterministic seed (→ testing.md) |
| Works on PostgreSQL, breaks on MySQL, SQLite, or MongoDB | Provider capability gap, not a Prisma bug (→ providers.md) |
| Anything else | Turn on query logging and read the SQL Prisma actually sent, then run that SQL by hand: the answer is almost always in the gap between what you expressed and what was emitted (→ performance.md) |
Depth on demand, by phase:
- Model —
schema.mdrelations, referential actions, keys, indexes, types, multi-tenancy ·providers.mdwhat PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and PlanetScale each refuse to do - Change —
migrations.mdpush vs migrate, renames, baselining, drift, zero-downtime sequences ·testing.mdtest databases, isolation, seeding, mocking ·deployment.mdgenerate in CI and Docker, engines, serverless, edge, monorepos - Query —
queries.mdfilters, nested writes, upsert, pagination, aggregation ·performance.mdrelation loading, indexes, counts, logging, payload size ·transactions.mdbatch vs interactive, isolation, retries, optimistic locking ·raw-sql.md$queryRaw, TypedSQL, safe interpolation - Extend and type —
extensions.md$extendsfor soft delete, audit, RLS, computed fields ·typescript.mdgenerated types, payload types, JSON, Decimal, BigInt - Operate —
connections.mdpool sizing, PgBouncer, serverless,directUrl·errors.mdevery P-code to cause and fix
Core Rules
- One migration command per environment, and never the other one.
db pushfor a throwaway prototype (no history, silently drops columns to converge);migrate devin development only (generates SQL, needs a shadow database, may reset);migrate deployin CI and production (applies existing files, never generates, never resets). Check: grep the deploy pipeline — amigrate devor adb pushthere is an incident waiting for its trigger. - The client is generated code, not a schema reader. Every schema edit needs
npx prisma generate, and every install path needs it too (postinstall script plus an explicit step in the build). The signature failure: types compile locally, production throws "Unknown argument" or a model that exists in the schema is missing on the client, because a cachednode_modulesshipped a client generated from an older schema. - Rename with
@map, never by renaming the field. Prisma diffs by name, so renamingfullNametonameemitsDROP COLUMN+ADD COLUMN: every row loses that value. Keep the column and rename only in Prisma (name String @map("fullName")), or hand-edit the generated migration toALTER TABLE ... RENAME COLUMNbefore it is applied. Same rule for models (@@map). - Index the foreign key yourself on PostgreSQL. Prisma migrate creates indexes for
@idand@uniqueonly. MySQL auto-indexes FK columns; PostgreSQL does not — so@@index([authorId])is your job, and without it awhere: { authorId }filter or a cascading parent delete scans the child table. UnderrelationMode = "prisma"(PlanetScale and friends) there are no FK constraints at all and the index is mandatory on every relation scalar. includecosts one query per relation; loops cost one per row. Queries = 1 + one per distinct relation at each nesting level:findManywith three includes is 4 round trips whether it returns 10 rows or 10,000. The N+1 you actually have comes from a loop or a GraphQL resolver — with one exception:findUnique/findUniqueOrThrowcalls on the same model in the same event-loop tick are batched into a singleWHERE id IN (...). Nothing else batches.undefinedmeans "ignore this filter",nullmeans "match NULL".deleteMany({ where: { tenantId: undefined } })is a full-table delete, andfindFirst({ where: { email: undefined } })returns a stranger's row. Rule: never let a possibly-undefined variable reach awhere. Validate first, or make the skip explicit withPrisma.skipunder thestrictUndefinedCheckspreview (prisma >=5.20), which turns implicitundefinedinto an error.- An interactive transaction holds a pooled connection for its entire body. Defaults:
timeout5000 ms,maxWait2000 ms. No HTTP calls, no queues, no user input inside it. Concurrency ceiling is the pool: withconnection_limit=5, the sixth concurrent interactive transaction waits and then fails P2028 aftermaxWait(2 s by default) — the app looks "deadlocked" while the database is idle. Ordinary queries queueing for the same pool fail P2024 instead, atpool_timeout(10 s). - Size the pool against the database, not against hope. Prisma's default
connection_limitisnum_physical_cpus * 2 + 1per client instance. Budget:connection_limit ≤ (max_connections − 3 reserved − other consumers) / expected instances. PostgreSQL shipsmax_connections = 100with 3 reserved, so on a 4-core runtime (9 connections each) eleven instances ask for 99 against the 97 available, and the eleventh gets P1001. Serverless: 1-2 plus an external pooler (connections.md). - Retry only the codes that are retryable. P2034 (write conflict / deadlock) and P2024 (pool timeout) deserve a retry of the whole transaction with jitter, capped at 3 attempts; P2002 deserves exactly one retry when it came from an upsert race, and zero when the duplicate is real. Never retry P2003, P2025 or any P1012 — nothing about a second attempt changes them.
Error Codes
Codes are stable across versions; message text is not. Match on e.code after narrowing with e instanceof Prisma.PrismaClientKnownRequestError. Full catalog with causes and fixes: errors.md.
| Code | Meaning | First move |
|---|---|---|
| P1001 | Can't reach database server | Host/port/SSL or network, not Prisma — test the same URL with a plain client |
| P1017 | Server has closed the connection | Idle timeout or a pooler killing sessions mid-flight (→ connections.md) |
| P2002 | Unique constraint failed | Read meta.target for the field, then decide: duplicate data or upsert race (rule 9) |
| P2003 | Foreign key constraint failed | Parent missing, or delete order wrong — check the referential action, not the query |
| P2025 | Record to update/delete not found | Row gone, or your extra where filter did not match (the optimistic-lock signal) |
| P2024 | Timed out fetching a new connection from the pool | Pool exhausted; default pool timeout is 10s (→ connections.md) |
| P2028 | Transaction API error | Usually a transaction used after commit, or maxWait exceeded |
| P2034 | Write conflict or deadlock | Expected under contention: retry the whole transaction (rule 9) |
| P3009 | Failed migration found in the history | Deploys stay blocked until migrate resolve records the decision |
| P3005 | Database schema is not empty | You need a baseline migration, not a first migration (→ migrations.md) |
Relation Loading
- Nothing is loaded implicitly. A relation you did not
includeorselectisundefinedat runtime and absent from the type — which is why the type error and the runtime bug appear together. includereturns the full scalar set of the relation;selectinsideincludeprunes it. On wide rows the difference is bytes over the wire per row, and it is the cheapest optimization in the list.selectandincludeare mutually exclusive at the same level. Nest them instead:include: { posts: { select: { id: true, title: true } } }.- Filtered relations (
include: { posts: { where: { published: true }, take: 5 } }) push the filter into the relation query — do this instead of loading everything and filtering in JavaScript. - Relation counts belong to the same round trip:
select: { _count: { select: { posts: true } } }. Aposts.lengthafter loading every post is the same answer with the whole table in memory. - Relation load strategy is selectable where supported (
relationLoadStrategy: "join" | "query",prisma >=5.7with therelationJoinspreview on PostgreSQL and MySQL):joinis one round trip with JSON aggregation,queryis one query per relation with a smaller, simpler payload. Measure both on a real dataset — deep nesting favorsjoinon a distant database, wide relations favorquery. Details and current status:performance.md.
Connection Budget
Formula, applied before touching any other performance knob:
total connections = client instances × connection_limit
must satisfy: total ≤ max_connections − reserved − other consumers
- Client instances are processes, not requests: one Node server = 1; a clustered server = 1 per worker; serverless = 1 per warm sandbox, and the count is set by your traffic, not by you.
connection_limitis a URL parameter:?connection_limit=10&pool_timeout=20. Raising it does not create database capacity — it decides who queues where.- Serverless without a pooler is the classic outage: every cold start opens its own pool and nothing gives them back. Use PgBouncer, a provider pooler, or Prisma Accelerate, and keep
connection_limitat 1-2 (→connections.md). - Transaction-mode poolers require
?pgbouncer=true(Prisma stops using named prepared statements) and a separatedirectUrlfor migrations, which need a real session.
Configuration
User-dependent variables. Defaults apply until the user states a preference; store them in ~/Clawic/data/prisma/config.yaml.
| Variable | Type | Default | Effect |
|---|---|---|---|
| provider | postgresql | mysql | sqlite | sqlserver | mongodb | cockroachdb | postgresql | Selects every provider-gated answer: FK indexing, mode: "insensitive", arrays, enums, JSON path syntax, skipDuplicates (→ providers.md) |
| prisma_major | number (5-6) | 6 | Which version-gated features are offered (createManyAndReturn, TypedSQL, omit, Prisma.skip) when the installed version is unknown |
| pooler | none | pgbouncer | supavisor | provider-pooler | accelerate | none | Whether URLs carry pgbouncer=true, whether directUrl is required, and the recommended connection_limit |
| deploy_target | node-server | serverless | edge | docker | node-server | Drives the client-instantiation pattern, binaryTargets, $disconnect advice and generate placement (→ deployment.md) |
| migration_workflow | migrate | push | sql-first | migrate | Which command sequence is emitted for a schema change, and whether hand-written SQL files are the source of truth |
| id_style | cuid | uuid | uuidv7 | autoincrement | cuid | The @id default in every generated model and example |
| naming_convention | camel-with-map | db-native | camel-with-map | Whether generated models carry @map/@@map to snake_case database names or match the database verbatim |
| default_take | number (1-1000) | 50 | The pagination cap added to any findMany emitted without one (→ performance.md) |
| destructive_confirm | bool | true | migrate reset, db push --accept-data-loss, and deleteMany/updateMany without a where are emitted for review instead of run |
Preference areas — customizable dimensions; a stated preference is recorded in config.yaml and applied from then on:
- Tooling — package manager and runner,
prisma-client-jsvs the newer generator, validation library paired with Prisma (Zod, Valibot), seeding tool - Thresholds — default page size, transaction
timeoutandmaxWait, retry attempts and backoff, the query duration worth flagging in a review - Conventions — model and field naming, singular vs plural tables, soft-delete field name, timestamp columns, enum vs lookup table, schema file layout (single file vs folder)
- Platform — provider and version, hosting target, region distance to the database (which decides how much round-trip count matters), monorepo layout and client output path
- Risk posture — whether migrations are applied directly or handed back as SQL for review, whether raw SQL is allowed at all, how strict the ban on
deleteManywithoutwhereis - Output format — schema plus explanation vs schema only, whether emitted queries carry the equivalent SQL in a comment, how much of the migration plan to narrate
- Work order — schema-first vs introspection-first, whether tests and seed data are updated in the same change as the migration
- Integrations — pooler and database host (Neon, Supabase, PlanetScale, RDS, Turso), Accelerate or a self-managed cache, observability stack for query logs
- Restrictions — tables Prisma must not manage (
@@ignore), compliance rules that forbid raw SQL or require audit logging, columns that must never be selected by default - Cadence — how often to re-run introspection against production, when to prune old migrations, review cycle for unused indexes
Output Gates
Before emitting a schema, a migration, or a query:
- Every relation scalar indexed (
@@index) unless the provider already indexes it, and everyrelationMode = "prisma"relation indexed without exception? - Every rename expressed as
@map/@@map, or the generated SQL hand-edited to a realRENAME? - The destructive step (drop column, drop table) split into a later migration, after the code that stopped using it shipped?
onDelete/onUpdatestated explicitly on every relation instead of inherited by default?- Money as
Decimal @db.Decimal(12,2), timestamps asDateTime @db.Timestamptz(3)where the provider has it, neverFloatfor money? - Does every
findManyhave atake, and everywherea value that cannot beundefined? - Does the emitted command match the environment (
migrate deployin CI, nevermigrate devordb push)? - Is
prisma generateguaranteed to run in this deployment path?
Traps
| Trap | Why it fails | Do instead |
|---|---|---|
db push on a database with real data |
Converges by dropping whatever does not match, and leaves no history to deploy elsewhere | migrate dev locally, migrate deploy everywhere else (rule 1) |
Building where from a request object |
Any absent key becomes undefined, which Prisma reads as "no filter" |
Validate into an explicit shape; Prisma.skip for deliberate skips (rule 6) |
new PrismaClient() per request or per module |
Each instance opens its own pool; the database hits its limit while the app looks idle | One instance per process, globalThis singleton in dev (→ connections.md) |
await forgotten on a query |
Prisma queries are lazy promises: nothing runs, no error, the value is a Promise | Lint with no-floating-promises — this is the one bug the type checker will not show you as a failure |
$transaction wrapped around a single nested write |
Nested writes are already one transaction; the wrapper only adds a held connection | Use the nested write alone (→ transactions.md) |
| Retrying an interactive transaction from inside itself | The transaction client is dead after the failure — P2028 on the retry | Retry the whole $transaction call from outside (rule 9) |
Soft delete implemented in middleware or a query extension |
Relation loads inside include do not pass through it: deleted children keep appearing |
Explicit filters, or a database view — the honest limits are in extensions.md |
createMany when you need the rows back |
Returns a count only, and skips nested creates entirely | createManyAndReturn (prisma >=5.14, not on MySQL) or a transaction of creates |
count() on a large table for a UI badge |
It is a full scan every render, and it is on the request path | Cached count, approximate count, or _count scoped to a relation (→ performance.md) |
String concatenation into $queryRawUnsafe |
SQL injection with the word "unsafe" already in the call | Tagged $queryRaw with Prisma.sql/Prisma.join (→ raw-sql.md) |
| Raw SQL used for writes that other code reads through Prisma | Raw bypasses @updatedAt, @default, extensions and middleware; rows come back with stale metadata |
Keep writes in the client, or set the columns yourself in the SQL |
@unique on a nullable column as a "one per user" rule |
SQL treats NULLs as distinct: unlimited NULL rows pass the constraint | Make it NOT NULL, or add a partial/filtered unique index in raw SQL |
| Enum values removed or reordered in a live schema | Rows holding the removed value break reads, and some engines cannot drop a value at all | Add-only enums, or a lookup table once the set churns (→ schema.md) |
Where Experts Disagree
cuidvsuuidvsbigintprimary keys. Random v4 UUIDs fragment the B-tree and widen every secondary index; sequential integers leak volume and complicate multi-writer merges. Boundary: externally visible IDs →cuid/UUIDv7 (time-ordered, still opaque); internal high-write tables →autoincrement()bigint. Switch on measured index bloat, not on aesthetics.- Schema-first vs introspection-first. Prisma's default is schema-first, and it is right when the application owns the database. When the database is shared with other systems, DBAs, or hand-written SQL,
db pullplus baselined migrations avoids fighting for ownership — the boundary is who is allowed to change the schema, not which is more modern. - How much raw SQL is acceptable. One camp keeps everything in the client for type safety and extension coverage; the other drops to
$queryRawat the first CTE or window function. Workable line: raw for read-only analytics and DDL-adjacent work, client for anything that writes rows other code reads back — because raw writes bypass@updatedAt, defaults, and every extension. - Prisma Migrate vs a plain SQL migration tool. Migrate is excellent at diffing and terrible at expressing operations SQL has and Prisma does not (concurrent indexes, partitioning, triggers). Teams past a certain size run Prisma for the schema and hand-written SQL for the change; that is a legitimate configuration (
migration_workflow: sql-first), not a defeat.
Related Skills
More Clawic skills, get them at https://clawic.com/skills/prisma (install if the user confirms):
pg— the PostgreSQL server underneath: plans, vacuum, locks, replicationsql— hand-written SQL, cross-engine portability, index and query designtypescript— type-system depth beyond Prisma's generated typesnodejs— process lifecycle, memory, and shutdown around the clientnextjs— where the client lives in App Router, server actions, and build-time generation
Feedback
- If useful, star it: https://clawic.com/skills/prisma
- Latest version: https://clawic.com/skills/prisma
Part of Clawic, the verified skill library. Get this skill: https://clawic.com/skills/prisma.