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
Same staged-transaction cap as elsewhere, hit on the bulk delete route: when transactionId is supplied, the whole bulk delete counts as ONE staged operation, and it is rejected with transaction_limit_exceeded when the transaction's 'operations' counter is already at the plan's databasesTransactionSize (default APP_LIMIT_DATABASE_TRANSACTION). The check happens before the transactionLogs entry is written, so the transaction state is unchanged.
Source
Thrown at src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php:132
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
// Handle transaction staging
if ($transactionId !== null) {
$transaction = $dbForProject->getDocument('transactions', $transactionId);
if ($transaction->isEmpty() || $transaction->getAttribute('status', '') !== 'pending') {
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction');
}
// 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
);
}
// Stage the operation in transaction logs
$staged = new Document([
'$id' => ID::unique(),
'databaseInternalId' => $database->getSequence(),
'collectionInternalId' => $collection->getSequence(),
'transactionInternalId' => $transaction->getSequence(),
'action' => 'bulkDelete',
'data' => [
'queries' => $originalQueries,
],
]);
$dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged) {View on GitHub (pinned to a1d520eea4)
Solutions
- Commit the current transaction and open a new one for the bulk delete
- Track staged operations client-side and rotate transactions before the cap
- If the workflow must be atomic beyond the cap, split into multiple committed transactions or negotiate a higher databasesTransactionSize
Example fix
// before: staging a bulk delete into an already-full transaction
await sdk.databases.deleteDocuments(dbId, colId, queries, fullTxId);
// after: rotate transactions at the plan limit before staging
if (stagedCount >= TX_LIMIT) {
await commitTransaction(txId);
txId = (await createTransaction()).$id;
stagedCount = 0;
}
await sdk.databases.deleteDocuments(dbId, colId, queries, txId);
stagedCount++; Defensive patterns
Strategy: try-catch
Validate before calling
const res = await fetch(`${endpoint}/databases/transactions/${txId}`, {
headers: { 'X-Appwrite-Project': projectId, 'X-Appwrite-Key': apiKey },
});
const tx = await res.json();
if (tx.status !== 'pending' || (tx.operations + 1) > TX_LIMIT) {
await commitTransaction(txId);
txId = (await createTransaction()).$id;
}
await sdk.databases.deleteDocuments(dbId, colId, queries, txId); Try / catch
try {
await sdk.databases.deleteDocuments(dbId, colId, queries, txId);
} catch (e) {
if (e.code === 'transaction_limit_exceeded') {
await commitTransaction(txId);
const tx = await createTransaction();
await sdk.databases.deleteDocuments(dbId, colId, queries, tx.$id); // a bulk op counts as ONE staged operation
} else throw e;
} Prevention
- Remember each staged bulk operation counts as exactly one operation regardless of matched document count
- Rotate transactions at the plan's databasesTransactionSize instead of waiting for the rejection
- Keep staged-operation counters in the orchestrating client, not in ad-hoc code paths
When it happens
Trigger: Staging a bulk delete into a transaction that already holds the maximum staged operations (e.g. after many single-document creates/updates/deletes/increments staged against it).
Common situations: Composite workflows staging create + update + bulk phases into one transaction; sync jobs that lose count of staged ops; default self-hosted limits being smaller than the job's write count.
Related errors
- transaction_limit_exceeded
- transaction_limit_exceeded
- transaction_limit_exceeded
- transaction_limit_exceeded
- transaction_limit_exceeded
AI-assisted analysis of appwrite/appwrite@a1d520eea4 (2026-08-18).
Data as JSON: /api/errors/6a4dea0047924bc2.
Report an issue: GitHub.