The Prisma P2002 error occurs when you attempt to insert or update a record with a value that violates a unique constraint in your database. This happens when trying to create a duplicate entry for a field marked as @unique, such as an email address that already exists. The fix typically involves checking for existing records before insertion or using upsert operations.
The P2002 error in Prisma indicates a unique constraint violation at the database level. This error is thrown by Prisma Client as a PrismaClientKnownRequestError when you try to create or update a record with a value that must be unique but already exists in the database. Unique constraints are database-level rules that ensure data integrity by preventing duplicate values in specific fields or combinations of fields. For example, if your User model has an email field marked with @unique, attempting to create two users with the same email address will trigger the P2002 error. This error is part of the Prisma error code system (P2000-P2099 range for query engine errors) and includes metadata about which field or fields caused the violation. The error helps maintain data consistency and is a fundamental aspect of relational database integrity. The error message typically includes the constraint name and the target fields, making it easier to identify exactly which unique constraint was violated and where the duplicate value exists.
Examine the error object to identify which field or fields violated the unique constraint:
import { Prisma } from '@prisma/client';
try {
await prisma.user.create({
data: {
email: '[email protected]',
name: 'Alice'
}
});
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
if (error.code === 'P2002') {
console.log('Unique constraint failed on:', error.meta?.target);
// Output: Unique constraint failed on: ['email']
}
}
}The error.meta.target property contains an array of field names that caused the violation. This helps you understand exactly which unique constraint was broken.
Note: Some database adapters (like PlanetScale/Vitess or Neon in certain configurations) may not provide the field name in the error metadata.
Prevent P2002 errors by checking if a record with the same unique value already exists:
async function createUserSafely(email: string, name: string) {
// Check if user with this email already exists
const existingUser = await prisma.user.findUnique({
where: { email }
});
if (existingUser) {
throw new Error(`User with email ${email} already exists`);
// Or return the existing user
// return existingUser;
}
// Safe to create now
return await prisma.user.create({
data: { email, name }
});
}This approach prevents the error but requires an additional database query. For better performance, consider using upsert (see next step).
The upsert operation atomically creates a record if it doesn't exist or updates it if it does:
const user = await prisma.user.upsert({
where: { email: '[email protected]' },
update: {
name: 'Alice Updated'
},
create: {
email: '[email protected]',
name: 'Alice'
}
});This is ideal when:
- You want to create a record if it doesn't exist
- You want to update it if it does exist
- You want to avoid the P2002 error entirely
For compound unique constraints:
// Schema: @@unique([userId, postId])
const like = await prisma.like.upsert({
where: {
userId_postId: {
userId: 1,
postId: 100
}
},
update: {
createdAt: new Date()
},
create: {
userId: 1,
postId: 100
}
});Note: Under high concurrency, upsert may still throw P2002 errors due to race conditions. Handle these with retry logic (see step 5).
Implement proper error handling to provide meaningful feedback:
import { Prisma } from '@prisma/client';
async function createUser(email: string, name: string) {
try {
return await prisma.user.create({
data: { email, name }
});
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
if (error.code === 'P2002') {
const target = error.meta?.target as string[] | undefined;
const field = target?.join(', ') || 'unknown field';
throw new Error(
`A user with this ${field} already exists. Please use a different value.`
);
}
}
throw error;
}
}For API responses:
app.post('/users', async (req, res) => {
try {
const user = await createUser(req.body.email, req.body.name);
res.status(201).json(user);
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2002') {
return res.status(409).json({
error: 'Conflict',
message: 'A user with this email already exists'
});
}
res.status(500).json({ error: 'Internal server error' });
}
});Race conditions can occur when multiple operations attempt to create the same unique value simultaneously. Implement retry logic:
async function upsertWithRetry<T>(
operation: () => Promise<T>,
maxRetries: number = 3,
delay: number = 100
): Promise<T> {
let lastError: any;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError &&
error.code === 'P2002') {
lastError = error;
// Wait before retrying with exponential backoff
await new Promise(resolve =>
setTimeout(resolve, delay * Math.pow(2, attempt))
);
continue;
}
throw error;
}
}
throw lastError;
}
// Usage
const user = await upsertWithRetry(() =>
prisma.user.upsert({
where: { email: '[email protected]' },
update: { name: 'Alice' },
create: { email: '[email protected]', name: 'Alice' }
})
);This is especially important for:
- High-traffic applications
- Concurrent user registrations
- Distributed systems
- Background job processing
Verify that your unique constraints are correctly defined in your Prisma schema:
model User {
id String @id @default(cuid())
email String @unique // Single field unique constraint
username String @unique
name String
}
model Post {
id String @id @default(cuid())
title String
slug String
authorId String
// Compound unique constraint - combination must be unique
@@unique([slug, authorId])
}
model UserProfile {
userId String @unique // One-to-one relation
bio String?
user User @relation(fields: [userId], references: [id])
}Key considerations:
- Use @unique for single field constraints
- Use @@unique([field1, field2]) for compound constraints
- Ensure unique constraints match your business logic
- Consider case-sensitivity: some databases treat '[email protected]' and '[email protected]' as different
After modifying unique constraints, create a migration:
npx prisma migrate dev --name add-unique-constraintDatabase-Specific Behavior: Different databases handle unique constraints differently:
- PostgreSQL: Case-sensitive unique constraints by default. Use LOWER(email) in a unique index for case-insensitive uniqueness
- MySQL: Case-insensitive for VARCHAR by default (depends on collation)
- SQLite: Case-sensitive unique constraints
- SQL Server: NULL values are considered distinct in unique constraints (multiple NULLs allowed)
Performance Optimization:
- Unique constraints automatically create indexes, improving query performance
- Use findUnique instead of findFirst when querying unique fields (more efficient)
- For existence checks, use select: { id: true } to minimize data transfer
- Consider database-level unique indexes for fields that aren't in your Prisma schema
Compound Unique Constraints: When using @@unique([field1, field2]), the where clause must include all fields:
// Correct
await prisma.like.upsert({
where: {
userId_postId: { userId: 1, postId: 100 }
},
// ...
});
// Incorrect - will not work
await prisma.like.upsert({
where: { userId: 1 }, // Missing postId
// ...
});Testing Strategies: Write tests to ensure unique constraint violations are handled:
describe('User creation', () => {
it('should prevent duplicate emails', async () => {
await prisma.user.create({
data: { email: '[email protected]', name: 'Test' }
});
await expect(
prisma.user.create({
data: { email: '[email protected]', name: 'Test2' }
})
).rejects.toThrow(/P2002/);
});
});Partial Unique Indexes: Some databases support conditional unique constraints (not directly in Prisma schema but via raw SQL):
-- PostgreSQL: Unique only for non-deleted users
CREATE UNIQUE INDEX unique_active_emails
ON "User" (email)
WHERE "deletedAt" IS NULL;Migration Considerations: When adding unique constraints to existing data:
1. Check for existing duplicates first
2. Clean up duplicate data before adding the constraint
3. Consider using a multi-step migration to handle large datasets
Prisma Version Features:
- Prisma 2.20+: Improved error messages with field information
- Prisma 4.0+: Better error metadata and debugging capabilities
- Always check error.meta.target for the specific fields that caused the violation
P6005: Invalid parameters (Pulse)
How to fix "P6005: Invalid parameters (Pulse)" in Prisma
P2011: Null constraint violation on the field
How to fix "P2011: Null constraint violation" in Prisma
P2009: Failed to validate the query: {validation_error}
How to fix "P2009: Failed to validate the query" in Prisma
P2007: Data validation error
How to fix "P2007: Data validation error" in Prisma
P1013: The provided database string is invalid
The provided database string is invalid