appwrite/appwrite · error · Appwrite\Extend\Exception
transaction_limit_exceeded
transaction_limit_exceeded
Error message
Transaction already has {existing} operations, adding 1 would exceed the maximum of {maxBatch} What it means
Thrown when adding one more operation would exceed the maximum number of operations allowed in a single Appwrite transaction. The limit comes from the project plan's databasesTransactionSize setting, falling back to the APP_LIMIT_DATABASE_TRANSACTION constant. The current count is read from the transaction's operations attribute, and staging is rejected if existing + 1 exceeds the cap.
Source
Thrown at src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php:418
: $dbForProject->getDocument('transactions', $transactionId);
if ($transaction->isEmpty()) {
throw new Exception(Exception::TRANSACTION_NOT_FOUND, params: [$transactionId]);
}
if ($transaction->getAttribute('status', '') !== 'pending') {
throw new Exception(Exception::TRANSACTION_NOT_READY);
}
$now = new \DateTime();
$expiresAt = new \DateTime($transaction->getAttribute('expiresAt', 'now'));
if ($now > $expiresAt) {
throw new Exception(Exception::TRANSACTION_EXPIRED);
}
// Enforce max operations per transaction
$maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION;
$existing = $transaction->getAttribute('operations', 0);
if (($existing + 1) > $maxBatch) {
throw new Exception(
Exception::TRANSACTION_LIMIT_EXCEEDED,
'Transaction already has ' . $existing . ' operations, adding 1 would exceed the maximum of ' . $maxBatch
);
}
$staged = new Document([
'$id' => ID::unique(),
'databaseInternalId' => $database->getSequence(),
'collectionInternalId' => $collection->getSequence(),
'transactionInternalId' => $transaction->getSequence(),
'documentId' => $isBulk ? null : $documentId,
'action' => $isBulk ? 'bulkCreate' : 'create',
'data' => $isBulk ? $documents : $documents[0],
]);
$dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged) {
$dbForProject->createDocument('transactionLogs', $staged);
$dbForProject->increaseDocumentAttribute(View on GitHub (pinned to feb9831e60)
Solutions
- Commit the current transaction and continue remaining operations in a new transaction (chunk the work)
- Read the transaction's operations attribute before staging and stop before hitting the cap
- Raise the plan's databasesTransactionSize limit (upgrade) if the workload legitimately needs larger transactions
- Reduce operations per transaction by batching document writes where the API supports it
Example fix
// before
for (const doc of docs) {
await db.createDocument('db', 'coll', ID.unique(), doc, undefined, undefined, undefined, undefined, txId); // fails at limit
}
// after
const CHUNK = 50;
for (let i = 0; i < docs.length; i += CHUNK) {
const tx = await dbTransactions.create();
for (const doc of docs.slice(i, i + CHUNK)) {
await db.createDocument('db', 'coll', ID.unique(), doc, undefined, undefined, undefined, undefined, tx.$id);
}
await dbTransactions.commit(tx.$id);
} Defensive patterns
Strategy: validation
Validate before calling
const tx = await dbTransactions.get(txId);
const MAX = 100; // keep in sync with plan's databasesTransactionSize
if ((tx.operations ?? 0) + 1 > MAX) {
throw new Error(`operation cap reached (${tx.operations}) — commit and open a new transaction`);
} Type guard
function isTransactionLimitExceeded(e) {
return e?.type === 'transaction_limit_exceeded';
} Try / catch
try {
await db.createDocument('db', 'coll', ID.unique(), payload, undefined, undefined, undefined, undefined, txId);
} catch (e) {
if (isTransactionLimitExceeded(e)) {
await dbTransactions.commit(txId);
const tx = await dbTransactions.create();
await db.createDocument('db', 'coll', ID.unique(), payload, undefined, undefined, undefined, undefined, tx.$id);
} else throw e;
} Prevention
- Know your plan's databasesTransactionSize before designing batches
- Track staged-operation counts client-side and chunk below the cap
- Make each staged operation idempotent so a re-staged batch is safe
- Commit as soon as a logical unit of work completes
When it happens
Trigger: POST /v1/databases/{databaseId}/collections/{collectionId}/documents with a transactionId when the transaction already holds exactly its maximum number of staged operations (e.g. plan limit reached by earlier creates/updates/deletes in the same transaction).
Common situations: Bulk import scripts staging hundreds of operations into one transaction on a plan with a small databasesTransactionSize; mixing bulk creates (which may count differently) with other staged ops; assuming the default limit applies while the project plan sets a lower one.
Related errors
- transaction_limit_exceeded
- transaction_limit_exceeded
- attribute_limit_exceeded
- transaction_limit_exceeded
- transaction_limit_exceeded
AI-assisted analysis of appwrite/appwrite@feb9831e60 (2026-08-18).
Data as JSON: /api/errors/5dc09115151281fb.
Report an issue: GitHub.