Claude Code with Prisma: TypeScript ORM Workflows
Why Prisma and Claude Code are a natural fit
Prisma is one of the tools that benefits most from Claude Code integration. The schema file is human-readable, declarative, and structured in a way that Claude can parse and extend reliably. The Prisma CLI commands have clear safety boundaries (dev versus deploy versus reset). And the generated client is fully typed, which means Claude gets compile-time feedback on every query it writes.
The result is that a properly configured Claude Code setup with Prisma produces schema changes, migrations, and query code at a quality level that is hard to reach with other ORMs. The schema file becomes a shared language between you and Claude. You describe the domain, Claude writes the Prisma schema. Claude writes a migration, Prisma validates it. Claude writes a query, TypeScript catches any type mismatch.
The qualifier is "properly configured." Without a project-specific CLAUDE.md, Claude will run prisma migrate dev as a side effect of any schema change, use prisma db push in contexts where it should not, generate seed scripts that overwrite existing data, and write queries that miss eager loading for required relations. Each of these is avoidable.
This guide covers the CLAUDE.md configuration and patterns that prevent those failure modes. If you are setting up Claude Code for the first time, the Claude Code setup guide covers installation and authentication before any of this applies.
The Prisma CLAUDE.md
The CLAUDE.md at your project root is read before every Claude Code session. For a Prisma project, it needs to answer: what version of Prisma and Node are in use, where is the schema, how is the client initialized, how are migrations applied, what are the seeding rules, and what are the hard no-ops?
# Prisma project rules
## Stack
- Node: 20.x
- TypeScript: 5.x
- Prisma: 5.x (schema in prisma/schema.prisma)
- Database: PostgreSQL 16 (local: postgresql://localhost:5432/myapp_dev)
- PrismaClient: singleton in lib/prisma.ts, never instantiate with `new PrismaClient()` outside this file
## Project structure
- Schema: prisma/schema.prisma
- Migrations: prisma/migrations/ (auto-generated, do not hand-edit)
- Seed script: prisma/seed.ts
- Prisma client: lib/prisma.ts (singleton export)
- Types: lib/types.ts for Prisma-derived types (Prisma.UserWithPosts etc.)
## Prisma commands
- Schema change workflow: edit prisma/schema.prisma → `npx prisma migrate dev --name <description>`
- Apply in CI/CD: `npx prisma migrate deploy` (not migrate dev)
- Format schema: `npx prisma format` after any schema edit
- Generate client: `npx prisma generate` (runs automatically after migrate dev)
- Validate schema: `npx prisma validate` before committing
## Hard rules
- NEVER run `prisma db push` on this project, migrations only
- NEVER run `prisma migrate reset` without explicit user approval
- NEVER run `prisma db seed` in production context
- NEVER instantiate PrismaClient outside lib/prisma.ts
- ALWAYS run `prisma format` after editing the schema
- ALWAYS run `prisma validate` before writing migration notes
Three sections in this CLAUDE.md prevent the most common Claude Code failures with Prisma.
The commands section is the most important. prisma db push is fast and convenient for prototyping but it bypasses the migration history, which means production cannot be brought in sync via migrate deploy. Claude will choose db push if not told otherwise, because it is the faster command for iteration. The explicit rule removes that choice.
The migrate reset rule matters for existing projects. Claude may suggest prisma migrate reset when debugging migration drift. That command drops and recreates the database. On a development database with non-trivial seed data, it is destructive. On a production database, it is catastrophic. The "explicit user approval" guard means Claude will surface the suggestion and wait rather than running it.
The singleton rule prevents the Prisma connection pooling issue that is most common in Next.js and similar server environments. Each new PrismaClient() opens a new connection pool. In development with hot reload, this exhausts connections quickly. Specifying lib/prisma.ts as the only valid instantiation location means Claude will import from there rather than instantiating inline.
Schema design with Claude Code
Prisma's schema format is where Claude Code produces the most immediate value. Describing a domain in natural language and having Claude generate a well-structured schema file is faster than writing it by hand, and Claude applies the right patterns for indexes, relations, and field types without being prompted.
Add a schema conventions section to your CLAUDE.md:
## Schema conventions
### Model naming: PascalCase singular (User, not Users or user)
### Field naming: camelCase (createdAt, not created_at)
### Required: every model has id, createdAt, updatedAt
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
authorId Int
author User @relation(fields: [authorId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([authorId])
@@index([published, createdAt])
}
### Index rules
- Foreign key fields always have an @index
- Fields used in WHERE clauses with high cardinality get @index
- Composite indexes for common query patterns (filter + sort)
- Unique constraints on business-key fields (email, slug)
With this template in CLAUDE.md, Claude generates schemas that match your conventions from the first attempt. The index annotations are particularly valuable: Claude will add @@index([authorId]) on the foreign key field and suggest compound indexes based on the query patterns you describe, rather than leaving index design entirely to you.
The @updatedAt decorator is easy to forget and Claude will apply it consistently once it sees the pattern in your CLAUDE.md template. Without it, you end up with updatedAt fields that are set on creation but never change.
Migration safety patterns
Migrations are where Claude Code needs the most guardrails. A schema change is low-risk. A migration that runs ALTER TABLE on a large production table, drops a column with data, or renames a relation without a transition plan is not.
Add to CLAUDE.md:
## Migration workflow
### Steps for every schema change
1. Edit prisma/schema.prisma
2. Run `npx prisma validate`, fix any errors before proceeding
3. Run `npx prisma format`
4. Run `npx prisma migrate dev --name <descriptive-kebab-case-name>`
- Name must describe the change: add-user-roles, add-post-slug-index, remove-deprecated-token-field
- Not: update1, fix, change
5. Review the generated SQL in prisma/migrations/<timestamp>_<name>/migration.sql before committing
6. Run tests: `npm test` or `npx jest`
### Destructive migration checklist (run before any migration that drops/renames)
- Does the column have data? (SELECT COUNT(*) FROM table WHERE column IS NOT NULL)
- Is there application code that reads this column? (grep codebase)
- Is there a data migration needed before the column is dropped?
- Is there a feature flag or deploy strategy to handle the transition?
### Safe rename pattern
-- Wrong: direct rename (Prisma may DROP and ADD)
-- Right: add new column → backfill data → update app to use new column → drop old column
-- Do this across separate migrations
The migration review step is the single most important addition. Claude will generate the migration SQL, and in most cases it will be correct. But reviewing the SQL before committing takes 30 seconds and catches the cases where Prisma interprets a schema change as a destructive operation when a non-destructive approach exists.
The descriptive naming rule also matters for the long-term maintenance of a project. A migration history full of add-column, update-schema, and fix-relations is useless for understanding what changed when. Descriptive names like add-user-email-verification-token and add-post-full-text-search-index create a human-readable change log.
Prisma client patterns
The generated Prisma client is where Claude Code has the most type safety assistance. Every query is typed, every relation access is explicit, and every missing include causes a TypeScript error. Claude respects these type boundaries when they are clear.
Add to CLAUDE.md:
## Query conventions
### Always use typed includes for relations
// Wrong: access relation without include (undefined at runtime)
const user = await prisma.user.findUnique({ where: { id } })
user.posts // undefined unless selected
// Right: explicit include
const user = await prisma.user.findUnique({
where: { id },
include: { posts: true }
})
### Type derived Prisma types in lib/types.ts
import { Prisma } from '@prisma/client'
export type UserWithPosts = Prisma.UserGetPayload<{
include: { posts: true }
}>
### findUnique vs findFirst vs findUniqueOrThrow
- findUnique: when you have an @unique or @id field, returns null if not found
- findFirst: when filtering on non-unique fields, returns null if not found
- findUniqueOrThrow: when not-found is always an error state, throws NotFoundError
- findFirstOrThrow: same pattern for non-unique queries
### Pagination: use cursor-based for large datasets
const posts = await prisma.post.findMany({
take: 20,
skip: cursor ? 1 : 0,
cursor: cursor ? { id: cursor } : undefined,
orderBy: { createdAt: 'desc' }
})
### Transactions for multi-table writes
const [user, profile] = await prisma.$transaction([
prisma.user.create({ data: userData }),
prisma.profile.create({ data: profileData })
])
The findUniqueOrThrow pattern is worth highlighting. Most Claude Code generation defaults to findUnique and then adds a null check. findUniqueOrThrow removes the null check entirely and throws a typed NotFoundError that you can catch at the HTTP layer and convert to a 404. Specifying this pattern in CLAUDE.md means Claude uses it automatically for lookups by ID.
The typed Prisma types section is important for TypeScript projects. Prisma.UserGetPayload produces a type that exactly matches the shape of a query with a specific include. Without it, Claude will write any types or manually construct interfaces that diverge from the Prisma schema over time. Centralizing derived types in lib/types.ts keeps them in sync with the schema automatically.
For related TypeScript database patterns and how Claude Code handles TypeScript projects more broadly, the Claude Code TypeScript guide covers the full TypeScript workflow that Prisma builds on.
Seeding with Claude Code
Database seeding is an area where Claude Code can cause subtle problems if not constrained. The default seed approach Claude generates will often upsert records, which is usually safe, but it may also deleteMany before creating records, or generate seeds that create foreign key violations if run in the wrong order.
Add to CLAUDE.md:
## Seed conventions (prisma/seed.ts)
### Safety first
- NEVER use deleteMany() in seed scripts
- NEVER use truncate
- Use upsert for all records (safe to run multiple times)
- Use a fixed, deterministic id or unique field for upsert where clause
- Seed in dependency order: create users before posts, categories before products
### Seed pattern
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient() // only allowed in seed.ts
async function main() {
const alice = await prisma.user.upsert({
where: { email: 'alice@example.com' },
update: {},
create: {
email: 'alice@example.com',
name: 'Alice',
},
})
await prisma.post.upsert({
where: { id: 1 },
update: {},
create: {
title: 'First post',
authorId: alice.id,
},
})
}
main()
.catch(console.error)
.finally(() => prisma.$disconnect())
### Run: npx prisma db seed
### Do NOT run in production environments
The upsert pattern with update: {} is the idempotent seeding approach. Running the seed script multiple times produces the same result without duplicates and without deleting data. The deleteMany alternative resets the database to a known state, which is useful for testing but destructive for shared development environments.
The prisma.$disconnect() in finally is a detail Claude sometimes omits. Without it, the Node process hangs after seeding completes. One line in the CLAUDE.md template ensures it is always included.
Permission hooks for Prisma operations
Claude Code's permission system allows you to gate destructive Prisma commands behind explicit approval. This is the safety layer between Claude suggesting a migration reset and it actually running.
In .claude/settings.local.json:
{
"permissions": {
"allow": [
"Bash(npx prisma generate)",
"Bash(npx prisma format)",
"Bash(npx prisma validate)",
"Bash(npx prisma studio*)",
"Bash(npx prisma migrate dev*)",
"Bash(npx prisma migrate status)",
"Bash(npx prisma migrate diff*)"
],
"deny": [
"Bash(npx prisma migrate reset*)",
"Bash(npx prisma db push*)",
"Bash(npx prisma db seed)",
"Bash(npx prisma migrate deploy*)"
]
}
}
This configuration allows Claude to do everything needed for development iteration: generate the client, format and validate the schema, create new migrations, and check migration status. It blocks the four dangerous operations: reset (drops the database), db push (bypasses migration history), db seed (modifies data), and deploy (production-only, should run in CI).
When Claude encounters a blocked command, it surfaces the suggestion with the reason and waits. You can approve by running the command directly in a terminal. This keeps Claude in the loop for planning without giving it unilateral access to destructive operations.
For a complete guide to how permission hooks work across any Claude Code project, the Claude Code hooks guide covers the full permission system including allow/deny patterns and bash hook configuration.
What to verify after Claude runs a migration
Claude Code generates migrations correctly in the common cases. A few patterns are worth verifying manually before committing.
Column type changes. Prisma may generate a migration that drops and recreates a column when you change a type (e.g., Int to BigInt, String to String @db.Text). The generated SQL is technically correct but drops any existing data in that column. Check the migration file: if you see DROP COLUMN followed by ADD COLUMN for the same column name, that is a destructive type change.
Relation renames. Renaming a relation field in the schema does not rename the underlying column, but it does update the generated client. If you rename a relation and also change the foreign key field name, Prisma may generate a DROP COLUMN and ADD COLUMN for the foreign key. Again, check the migration file before running.
Index additions on large tables. Adding an index to a large table takes an ACCESS SHARE lock in PostgreSQL. For tables with millions of rows, this blocks writes. The CONCURRENTLY option avoids the lock but must be written manually in the migration SQL. Prisma does not generate CREATE INDEX CONCURRENTLY automatically. If you are adding an index to a high-traffic table, edit the migration SQL to use CONCURRENTLY before running.
These checks take less than a minute per migration and prevent the categories of production incidents that come from applying AI-generated migrations without review.
Connecting Prisma to your broader Claude Code workflow
The patterns in this guide produce a Prisma setup where Claude Code can confidently write schema changes, generate typed queries, and maintain seed data without requiring constant correction. The CLAUDE.md configuration converts implicit project knowledge into explicit rules Claude reads before every session.
The underlying principle is the same across any Claude Code best practices workflow: Claude Code performs at the level of the context you provide. A Prisma project without CLAUDE.md configuration produces Claude that runs db push instead of migrations and forgets to include relations in queries. A project with the configuration above produces Claude that treats your schema as the source of truth, follows your migration safety process, and writes queries with correct includes and typed return types.
If you are using Prisma with Supabase as your PostgreSQL provider, the Claude Code Supabase guide covers the additional configuration for row-level security, Supabase client setup, and the differences between Prisma migrations and Supabase's own migration tooling.
Start with the CLAUDE.md template, add the permission hooks, and run through one migration cycle with Claude to verify the workflow. The investment in configuration pays back within the first day of active development. Claudify includes a Prisma-specific CLAUDE.md template as part of the Claude Code workflow kit, pre-configured with migration guardrails, typed query patterns, and safe seeding conventions for TypeScript projects.
More like this
Ready to upgrade your Claude Code setup?
Get Claudify