Transaction API
Prisma Client provides robust transaction support through the $transaction method, supporting both sequential batch transactions and interactive transactions.
$transaction Method
The $transaction method executes multiple operations as a single atomic unit.
$transaction(
input: PrismaPromise[] | ((tx: PrismaClient) => Promise<any>),
options?: TransactionOptions
): Promise<any>
Batch Transactions
Execute multiple operations sequentially in a single transaction.
Signature
$transaction(operations: PrismaPromise[]): Promise<any[]>
Basic Usage
const [user, posts] = await prisma.$transaction([
prisma.user.create({
data: { email: 'alice@prisma.io', name: 'Alice' }
}),
prisma.post.findMany({
where: { published: true }
})
])
Characteristics
Array of Prisma Client operations to execute in sequence.Do not await the operations before passing them to $transaction. Pass the promises directly.
// ✅ Correct
await prisma.$transaction([
prisma.user.create({ data }),
prisma.post.update({ where, data })
])
// ❌ Wrong - don't await individual operations
await prisma.$transaction([
await prisma.user.create({ data }),
await prisma.post.update({ where, data })
])
Returns an array containing the results of each operation in order.
Advanced Example
try {
const result = await prisma.$transaction([
// Transfer money between accounts
prisma.account.update({
where: { id: fromAccountId },
data: { balance: { decrement: amount } }
}),
prisma.account.update({
where: { id: toAccountId },
data: { balance: { increment: amount } }
}),
// Log the transaction
prisma.transactionLog.create({
data: {
from: fromAccountId,
to: toAccountId,
amount
}
})
])
console.log('Transaction completed:', result)
} catch (error) {
console.error('Transaction failed, all changes rolled back:', error)
}
Options
$transaction(
operations: PrismaPromise[],
options?: {
isolationLevel?: IsolationLevel
}
): Promise<any[]>
Database transaction isolation level.type IsolationLevel =
| 'ReadUncommitted'
| 'ReadCommitted'
| 'RepeatableRead'
| 'Serializable'
| 'Snapshot'
Example:await prisma.$transaction(
[operation1, operation2],
{ isolationLevel: 'Serializable' }
)
Available isolation levels depend on your database. Not all databases support all levels.
Interactive Transactions
Execute complex transaction logic with full control flow, including conditional operations and loops.
Signature
$transaction<R>(
callback: (tx: PrismaClient) => Promise<R>,
options?: {
maxWait?: number
timeout?: number
isolationLevel?: IsolationLevel
}
): Promise<R>
Basic Usage
const result = await prisma.$transaction(async (tx) => {
// All operations use 'tx' instead of 'prisma'
const user = await tx.user.create({
data: { email: 'alice@prisma.io', name: 'Alice' }
})
const posts = await tx.post.createMany({
data: [
{ title: 'Post 1', authorId: user.id },
{ title: 'Post 2', authorId: user.id }
]
})
return { user, posts }
})
Callback Parameter
callback
(tx: PrismaClient) => Promise<R>
required
Async function that receives a transaction-aware Prisma Client instance.The tx parameter is a special Prisma Client instance that:
- Executes all operations within the transaction
- Is isolated from the main Prisma Client instance
- Automatically commits on successful completion
- Automatically rolls back if an error is thrown
Always use the tx parameter inside the transaction callback, not the main prisma client.
Returns the value returned by the callback function.
Transaction Options
Maximum time (in milliseconds) to wait for the transaction to start.await prisma.$transaction(
async (tx) => { /* ... */ },
{ maxWait: 5000 } // Wait up to 5 seconds
)
Throws an error if the transaction cannot start within this time.
Maximum time (in milliseconds) for the transaction to complete.await prisma.$transaction(
async (tx) => { /* ... */ },
{ timeout: 10000 } // Transaction must complete within 10 seconds
)
The transaction is automatically rolled back if it exceeds this timeout.
Transaction isolation level (same as batch transactions).
Complete Options Example
const result = await prisma.$transaction(
async (tx) => {
// Your transaction logic
},
{
maxWait: 5000, // Wait up to 5s to start
timeout: 10000, // Must complete within 10s
isolationLevel: 'Serializable'
}
)
Advanced Usage
Conditional Operations
const result = await prisma.$transaction(async (tx) => {
const user = await tx.user.findUnique({
where: { email: 'alice@prisma.io' }
})
if (user) {
// Update existing user
return tx.user.update({
where: { id: user.id },
data: { lastLogin: new Date() }
})
} else {
// Create new user
return tx.user.create({
data: {
email: 'alice@prisma.io',
name: 'Alice'
}
})
}
})
Loops and Iterations
const userIds = [1, 2, 3, 4, 5]
const updatedUsers = await prisma.$transaction(async (tx) => {
const results = []
for (const userId of userIds) {
const user = await tx.user.update({
where: { id: userId },
data: { points: { increment: 10 } }
})
results.push(user)
}
return results
})
Error Handling
try {
await prisma.$transaction(async (tx) => {
const user = await tx.user.create({
data: { email: 'alice@prisma.io' }
})
// Check business logic
if (user.email.includes('spam')) {
// Throw to rollback
throw new Error('Email not allowed')
}
await tx.post.create({
data: { title: 'Welcome', authorId: user.id }
})
})
} catch (error) {
console.error('Transaction rolled back:', error)
// All changes are automatically rolled back
}
Using Query Results
const stats = await prisma.$transaction(async (tx) => {
const userCount = await tx.user.count()
const avgAge = await tx.user.aggregate({
_avg: { age: true }
})
// Create audit record based on results
await tx.auditLog.create({
data: {
event: 'STATS_CALCULATED',
metadata: {
userCount,
avgAge: avgAge._avg.age
}
}
})
return { userCount, avgAge: avgAge._avg.age }
})
Nested Transactions
Prisma supports nested interactive transactions using savepoints.
const result = await prisma.$transaction(async (outerTx) => {
const user = await outerTx.user.create({
data: { email: 'alice@prisma.io' }
})
// Nested transaction
const profile = await outerTx.$transaction(async (innerTx) => {
return innerTx.profile.create({
data: {
userId: user.id,
bio: 'Hello world'
}
})
})
return { user, profile }
})
Nested transactions use database savepoints internally. The outer transaction commits only if all nested transactions succeed.
MongoDB does not support nested transactions.
Transaction Isolation Levels
Different databases support different isolation levels:
| Isolation Level | PostgreSQL | MySQL | SQL Server | SQLite |
|---|
| ReadUncommitted | ✅ | ✅ | ✅ | ❌ |
| ReadCommitted | ✅ | ✅ | ✅ | ❌ |
| RepeatableRead | ✅ | ✅ | ✅ | ❌ |
| Serializable | ✅ | ✅ | ✅ | ✅ |
| Snapshot | ❌ | ❌ | ✅ | ❌ |
Isolation Level Descriptions
- ReadUncommitted: Lowest isolation, allows dirty reads
- ReadCommitted: Prevents dirty reads (default for most databases)
- RepeatableRead: Prevents dirty and non-repeatable reads
- Serializable: Highest isolation, prevents all phenomena
- Snapshot: SQL Server specific, provides repeatable read with row versioning
Database-Specific Notes
PostgreSQL
// PostgreSQL supports all standard isolation levels
await prisma.$transaction(
async (tx) => { /* ... */ },
{ isolationLevel: 'RepeatableRead' }
)
MySQL
// MySQL defaults to RepeatableRead
await prisma.$transaction(
async (tx) => { /* ... */ },
{ isolationLevel: 'ReadCommitted' }
)
SQLite
// SQLite only supports Serializable
await prisma.$transaction(async (tx) => {
// All operations are serializable by default
})
MongoDB
MongoDB has limited transaction support:
- Requires replica set configuration
- Does not support nested transactions
- Limited to batch transactions only
Cloudflare D1
Cloudflare D1 does not support interactive transactions. Use batch transactions only:// ✅ Supported
await prisma.$transaction([
prisma.user.create({ data }),
prisma.post.create({ data })
])
// ❌ Not supported
await prisma.$transaction(async (tx) => {
// This will throw an error
})
Best Practices
-
Keep transactions short: Long-running transactions hold database locks and can impact performance.
-
Use appropriate isolation levels: Higher isolation levels provide more consistency but reduce concurrency.
-
Handle errors explicitly: Always use try/catch when transactions might fail.
-
Avoid external API calls: Don’t make HTTP requests or other I/O inside transactions.
-
Use batch transactions when possible: They’re simpler and often more performant than interactive transactions.
-
Set appropriate timeouts: Adjust
maxWait and timeout based on your workload.
// ✅ Good: Short, focused transaction
await prisma.$transaction(async (tx) => {
const user = await tx.user.create({ data })
await tx.profile.create({ data: { userId: user.id } })
})
// ❌ Bad: External API call in transaction
await prisma.$transaction(async (tx) => {
const user = await tx.user.create({ data })
await fetch('https://api.example.com/notify') // Don't do this!
await tx.profile.create({ data: { userId: user.id } })
})
See Also