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

  1. Commit the current transaction and continue remaining operations in a new transaction (chunk the work)
  2. Read the transaction's operations attribute before staging and stop before hitting the cap
  3. Raise the plan's databasesTransactionSize limit (upgrade) if the workload legitimately needs larger transactions
  4. 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

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


AI-assisted analysis of appwrite/appwrite@feb9831e60 (2026-08-18). Data as JSON: /api/errors/5dc09115151281fb. Report an issue: GitHub.