Transaction

Read and write atomically with runTransaction.

The transaction guarantees that by the time we commit the write, the data on which we decided to act is still the same in the database (unchanged).

Outside a transaction, the data we read can change during the time window that separates the read hitting the database and the write hitting the database, and there is no check that prevents the write if the read data has changed.

Note: the Admin SDK locks the document during the read to write time-window, so there won't be retries. The client SDK doesn't lock the document. Instead, if data changes during the time window, a new read is done to account for the new value.

For example, if credits is positive and sufficient, we accept the purchase, but by the time we are about to commit the purchase, we want credits not to have changed since the read, otherwise we start the check process over again. This is the transaction pattern.

runTransaction

runTransaction expects a callback. transaction is a helper that holds the read and write methods (get, update, set).

Note that we await reads, but don't await writes, due to how runTransaction is implemented.

In case of failed preconditions, we abort the transaction with a throw.

Client SDK:

await runTransaction(db, async (transaction) => {
    // read
    const snapshot = await transaction.get(docRef)

    // check condition
    const currentCount = snapshot.data().count
    if (currentCount >= 10) throw Error("Sorry, event is full!") // Abort

    // proceed
    transaction.update(docRef, { count: currentCount + 1 })
})

Admin SDK:

await db.runTransaction(async (transaction) => {
    // identical API
})
earlymorning logo

© Antoine Weber 2026 - All rights reserved

Transaction

Read and write atomically with runTransaction.

The transaction guarantees that by the time we commit the write, the data on which we decided to act is still the same in the database (unchanged).

Outside a transaction, the data we read can change during the time window that separates the read hitting the database and the write hitting the database, and there is no check that prevents the write if the read data has changed.

Note: the Admin SDK locks the document during the read to write time-window, so there won't be retries. The client SDK doesn't lock the document. Instead, if data changes during the time window, a new read is done to account for the new value.

For example, if credits is positive and sufficient, we accept the purchase, but by the time we are about to commit the purchase, we want credits not to have changed since the read, otherwise we start the check process over again. This is the transaction pattern.

runTransaction

runTransaction expects a callback. transaction is a helper that holds the read and write methods (get, update, set).

Note that we await reads, but don't await writes, due to how runTransaction is implemented.

In case of failed preconditions, we abort the transaction with a throw.

Client SDK:

await runTransaction(db, async (transaction) => {
    // read
    const snapshot = await transaction.get(docRef)

    // check condition
    const currentCount = snapshot.data().count
    if (currentCount >= 10) throw Error("Sorry, event is full!") // Abort

    // proceed
    transaction.update(docRef, { count: currentCount + 1 })
})

Admin SDK:

await db.runTransaction(async (transaction) => {
    // identical API
})