Transactions and Batched Writes

Cloud Firestore supports transactions and batched writes for atomically reading and writing data. In a set of atomic operations, either all of the operations succeed, or none of them are applied. A transaction is an atomic set of Cloud Firestore operations on one or more documents. Similarly, batched writes are sets of write operations that complete atomically.

For example, transactions are useful when you want to update a field's value based on its current value, or the value of some other field. You could increment a counter by creating a transaction that reads the current value of the counter, increments it, and writes the new value to Cloud Firestore.

Updating data with transactions

Using the Cloud Firestore client libraries, you can group multiple operations into a single transaction. A transaction consists of any number of get() operations followed by any number of write operations such as set(), update(), or delete(). In the case of a concurrent edit, Cloud Firestore runs the entire transaction again. For example, if a transaction reads documents and another client modifies any of those documents, Cloud Firestore retries the transaction. This feature ensures that the transaction runs on up-to-date and consistent data.

Transactions never partially apply writes. All writes execute at the end of a successful transaction.

When using transactions, note that:

  • Read operations must come before write operations.
  • A function calling a transaction (transaction function) might run more than once if a concurrent edit affects a document that the transaction reads.
  • Transaction functions should not directly modify application state.
  • Transactions will fail when the client is offline.

The following example shows how to create and run a transaction:

Web
// Create a reference to the SF doc.
var sfDocRef = db.collection("cities").doc("SF");

// Uncomment to initialize the doc.
// sfDocRef.set({ population: 0 });

return db.runTransaction(function(transaction) {
    // This code may get re-run multiple times if there are conflicts.
    return transaction.get(sfDocRef).then(function(sfDoc) {
        var newPopulation = sfDoc.data().population + 1;
        transaction.update(sfDocRef, { population: newPopulation });
    });
}).then(function() {
    console.log("Transaction successfully committed!");
}).catch(function(error) {
    console.log("Transaction failed: ", error);
});
Swift
let sfReference = db.collection("cities").document("SF")

db.runTransaction({ (transaction, errorPointer) -> Any? in
    let sfDocument: DocumentSnapshot
    do {
        try sfDocument = transaction.getDocument(sfReference)
    } catch let fetchError as NSError {
        errorPointer?.pointee = fetchError
        return nil
    }

    guard let oldPopulation = sfDocument.data()["population"] as? Int else {
        let error = NSError(
            domain: "AppErrorDomain",
            code: -1,
            userInfo: [
                NSLocalizedDescriptionKey: "Unable to retrieve population from snapshot \(sfDocument)"
            ]
        )
        errorPointer?.pointee = error
        return nil
    }

    transaction.updateData(["population": oldPopulation + 1], forDocument: sfReference)
    return nil
}) { (object, error) in
    if let error = error {
        print("Transaction failed: \(error)")
    } else {
        print("Transaction successfully committed!")
    }
}
Objective-C
FIRDocumentReference *sfReference =
    [[self.db collectionWithPath:@"cities"] documentWithPath:@"SF"];
[self.db runTransactionWithBlock:^id (FIRTransaction *transaction, NSError **errorPointer) {
  FIRDocumentSnapshot *sfDocument = [transaction getDocument:sfReference error:errorPointer];
  if (*errorPointer != nil) { return nil; }

  if (![sfDocument.data[@"population"] isKindOfClass:[NSNumber class]]) {
    *errorPointer = [NSError errorWithDomain:@"AppErrorDomain" code:-1 userInfo:@{
      NSLocalizedDescriptionKey: @"Unable to retreive population from snapshot"
    }];
    return nil;
  }
  NSInteger oldPopulation = [sfDocument.data[@"population"] integerValue];

  [transaction updateData:@{ @"population": @(oldPopulation + 1) } forDocument:sfReference];

  return nil;
} completion:^(id result, NSError *error) {
  if (error != nil) {
    NSLog(@"Transaction failed: %@", error);
  } else {
    NSLog(@"Transaction successfully committed!");
  }
}];
  
Android
final DocumentReference sfDocRef = db.collection("cities").document("SF");

db.runTransaction(new Transaction.Function<Void>() {
    @Override
    public Void apply(Transaction transaction) throws FirebaseFirestoreException {
        DocumentSnapshot snapshot = transaction.get(sfDocRef);
        double newPopulation = snapshot.getDouble("population") + 1;
        transaction.update(sfDocRef, "population", newPopulation);

        // Success
        return null;
    }
}).addOnSuccessListener(new OnSuccessListener<Void>() {
    @Override
    public void onSuccess(Void aVoid) {
        Log.d(TAG, "Transaction success!");
    }
})
.addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception e) {
        Log.w(TAG, "Transaction failure.", e);
    }
});
Java
// Initialize doc
final DocumentReference docRef = db.collection("cities").document("SF");
City city = new City("SF");
city.setCountry("USA");
city.setPopulation(860000L);
docRef.set(city).get();

// run an asynchronous transaction
ApiFuture<Void> transaction =
    db.runTransaction(
        new Transaction.Function<Void>() {
          @Override
          public Void updateCallback(Transaction transaction) throws Exception {
            // retrieve document and increment population field
            DocumentSnapshot snapshot = transaction.get(docRef).get();
            long oldPopulation = snapshot.getLong("population");
            transaction.update(docRef, "population", oldPopulation + 1);
            return null;
          }
        });
// block on transaction operation using transaction.get()
Python
transaction = db.transaction()
city_ref = db.collection(u'cities').document(u'SF')

@firestore.transactional
def update_in_transaction(transaction, city_ref):
    snapshot = city_ref.get(transaction=transaction)
    transaction.update(city_ref, {
        u'population': snapshot.get(u'population') + 1
    })

update_in_transaction(transaction, city_ref)
Node.js
// Initialize document
var cityRef = db.collection('cities').doc('SF');
var setCity = cityRef.set({
    name: 'San Francisco',
    state: 'CA',
    country: 'USA',
    capital: false,
    population: 860000
});

var transaction = db.runTransaction(t => {
    return t.get(cityRef)
        .then(doc => {
            // Add one person to the city population
            var newPopulation = doc.data().population + 1;
            t.update(cityRef, { population: newPopulation });
        });
})
.then(result => {
    console.log('Transaction success!');
})
.catch(err => {
    console.log('Transaction failure:', err);
});
Go
ref := client.Collection("cities").Doc("SF")
err := client.RunTransaction(ctx, func(ctx context.Context, tx *firestore.Transaction) error {
	doc, err := tx.Get(ref) // tx.Get, NOT ref.Get!
	if err != nil {
		return err
	}
	pop, err := doc.DataAt("population")
	if err != nil {
		return err
	}
	return tx.UpdateMap(ref, map[string]interface{}{
		"population": pop.(int64) + 1,
	})
})
if err != nil {
	return err
}

Passing information out of transactions

Do not modify application state inside of your transaction functions. Doing so will introduce concurrency issues, because transaction functions can run multiple times and are not guaranteed to run on the UI thread. Instead, pass information you need out of your transaction functions. The following example builds on the previous example to show how to pass information out of a transaction:

Web
// Create a reference to the SF doc.
var sfDocRef = db.collection("cities").doc("SF");

db.runTransaction(function(transaction) {
    return transaction.get(sfDocRef).then(function(sfDoc) {
        var newPopulation = sfDoc.data().population + 1;
        if (newPopulation <= 1000000) {
        transaction.update(sfDocRef, { population: newPopulation });
        return newPopulation;
        } else {
        return Promise.reject("Sorry! Population is too big.");
        }
    });
}).then(function(newPopulation) {
    console.log("Population increased to ", newPopulation);
}).catch(function(err) {
    // This will be an "population is too big" error.
    console.error(err);
});
Swift
let sfReference = db.collection("cities").document("SF")

db.runTransaction({ (transaction, errorPointer) -> Any? in
    let sfDocument: DocumentSnapshot
    do {
        try sfDocument = transaction.getDocument(sfReference)
    } catch let fetchError as NSError {
        errorPointer?.pointee = fetchError
        return nil
    }

    guard let oldPopulation = sfDocument.data()["population"] as? Int else {
        let error = NSError(
            domain: "AppErrorDomain",
            code: -1,
            userInfo: [
                NSLocalizedDescriptionKey: "Unable to retrieve population from snapshot \(sfDocument)"
            ]
        )
        errorPointer?.pointee = error
        return nil
    }

    let newPopulation = oldPopulation + 1
    guard newPopulation <= 1000000 else {
        let error = NSError(
            domain: "AppErrorDomain",
            code: -2,
            userInfo: [NSLocalizedDescriptionKey: "Population \(newPopulation) too big"]
        )
        errorPointer?.pointee = error
        return nil
    }

    transaction.updateData(["population": newPopulation], forDocument: sfReference)
    return newPopulation
}) { (object, error) in
    if let error = error {
        print("Error updating population: \(error)")
    } else {
        print("Population increased to \(object!)")
    }
}
Objective-C
FIRDocumentReference *sfReference =
[[self.db collectionWithPath:@"cities"] documentWithPath:@"SF"];
[self.db runTransactionWithBlock:^id (FIRTransaction *transaction, NSError **errorPointer) {
  FIRDocumentSnapshot *sfDocument = [transaction getDocument:sfReference error:errorPointer];
  if (*errorPointer != nil) { return nil; }

  if (![sfDocument.data[@"population"] isKindOfClass:[NSNumber class]]) {
    *errorPointer = [NSError errorWithDomain:@"AppErrorDomain" code:-1 userInfo:@{
      NSLocalizedDescriptionKey: @"Unable to retreive population from snapshot"
    }];
    return nil;
  }
  NSInteger population = [sfDocument.data[@"population"] integerValue];

  population++;
  if (population >= 1000000) {
    *errorPointer = [NSError errorWithDomain:@"AppErrorDomain" code:-2 userInfo:@{
      NSLocalizedDescriptionKey: @"Population too big"
    }];
    return @(population);
  }

  [transaction updateData:@{ @"population": @(population) } forDocument:sfReference];

  return nil;
} completion:^(id result, NSError *error) {
  if (error != nil) {
    NSLog(@"Transaction failed: %@", error);
  } else {
    NSLog(@"Population increased to %@", result);
  }
}];
  
Android
final DocumentReference sfDocRef = db.collection("cities").document("SF");

db.runTransaction(new Transaction.Function<Double>() {
    @Override
    public Double apply(Transaction transaction) throws FirebaseFirestoreException {
        DocumentSnapshot snapshot = transaction.get(sfDocRef);
        double newPopulation = snapshot.getDouble("population") + 1;
        if (newPopulation <= 1000000) {
            transaction.update(sfDocRef, "population", newPopulation);
            return newPopulation;
        } else {
            throw new FirebaseFirestoreException("Population too high",
                    FirebaseFirestoreException.Code.ABORTED);
        }
    }
}).addOnSuccessListener(new OnSuccessListener<Double>() {
    @Override
    public void onSuccess(Double result) {
        Log.d(TAG, "Transaction success: " + result);
    }
})
.addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception e) {
        Log.w(TAG, "Transaction failure.", e);
    }
});
Java
final DocumentReference docRef = db.collection("cities").document("SF");
ApiFuture<String> transaction =
    db.runTransaction(
        new Transaction.Function<String>() {
          @Override
          public String updateCallback(Transaction transaction) throws Exception {
            DocumentSnapshot snapshot = transaction.get(docRef).get();
            Long newPopulation = snapshot.getLong("population") + 1;
            // conditionally update based on current population
            if (newPopulation <= 1000000L) {
              transaction.update(docRef, "population", newPopulation);
              return "Population increased to " + newPopulation;
            } else {
              throw new Exception("Sorry! Population is too big.");
            }
          }
        });
// Print information retrieved from transaction
System.out.println(transaction.get());
Python
transaction = db.transaction()
city_ref = db.collection(u'cities').document(u'SF')

@firestore.transactional
def update_in_transaction(transaction, city_ref):
    snapshot = city_ref.get(transaction=transaction)
    new_population = snapshot.get(u'population') + 1

    if new_population < 1000000:
        transaction.update(city_ref, {
            u'population': new_population
        })
        return True
    else:
        return False

result = update_in_transaction(transaction, city_ref)
if result:
    print(u'Population updated')
else:
    print(u'Sorry! Population is too big.')
Node.js
var cityRef = db.collection('cities').doc('SF');
var transaction = db.runTransaction(t => {
    return t.get(cityRef)
        .then(doc => {
            var newPopulation = doc.data().population + 1;
            if (newPopulation <= 1000000) {
                t.update(cityRef, { population: newPopulation });
                return Promise.resolve('Population increased to ' + newPopulation);
            } else {
                return Promise.reject('Sorry! Population is too big.');
            }
        });
})
.then(result => {
    console.log('Transaction success', result);
})
.catch(err => {
    console.log('Transaction failure:', err);
});
Go
ref := client.Collection("cities").Doc("SF")
err := client.RunTransaction(ctx, func(ctx context.Context, tx *firestore.Transaction) error {
	doc, err := tx.Get(ref)
	if err != nil {
		return err
	}
	pop, err := doc.DataAt("population")
	if err != nil {
		return err
	}
	newpop := pop.(int64) + 1
	if newpop <= 1000000 {
		return tx.UpdateMap(ref, map[string]interface{}{
			"population": pop.(int64) + 1,
		})
	}
	return errors.New("population is too big")
})
if err != nil {
	return err
}

Transaction failure

A transaction can fail for the following reasons:

  • The transaction contains read operations after write operations. Read operations must always come before any write operations.
  • The transaction read a document that was modified outside of the transaction. In this case, the transaction automatically runs again. The transaction is retried a finite number of times.

A failed transaction returns an error and does not write anything to the database. You do not need to roll back the transaction; Cloud Firestore does this automatically.

Batched writes

If you do not need to read any documents in your operation set, you can execute multiple write operations as a single batch that contains any combination of set(), update(), or delete() operations. A batch of writes completes atomically and can write to multiple documents.

Batched writes have fewer failure cases than transactions and use simpler code. They are not affected by contention issues, because they don't depend on consistently reading any documents. You can perform batched writes even when the user's device is offline. The following example shows how to build and commit a batch of writes:

Web
// Get a new write batch
var batch = db.batch();

// Set the value of 'NYC'
var nycRef = db.collection("cities").doc("NYC");
batch.set(nycRef, {name: "New York City"});

// Update the population of 'SF'
var sfRef = db.collection("cities").doc("SF");
batch.update(sfRef, {"population": 1000000});

// Delete the city 'LA'
var laRef = db.collection("cities").doc("LA");
batch.delete(laRef);

// Commit the batch
batch.commit().then(function () {
    // ...
});
Swift
// Get new write batch
let batch = db.batch()

// Set the value of 'NYC'
let nycRef = db.collection("cities").document("NYC")
batch.setData([:], forDocument: nycRef)

// Update the population of 'SF'
let sfRef = db.collection("cities").document("SF")
batch.updateData(["population": 1000000 ], forDocument: sfRef)

// Delete the city 'LA'
let laRef = db.collection("cities").document("LA")
batch.deleteDocument(laRef)

// Commit the batch
batch.commit() { err in
    if let err = err {
        print("Error writing batch \(err)")
    } else {
        print("Batch write succeeded.")
    }
}
Objective-C
// Get new write batch
FIRWriteBatch *batch = [self.db batch];

// Set the value of 'NYC'
FIRDocumentReference *nycRef =
    [[self.db collectionWithPath:@"cities"] documentWithPath:@"NYC"];
[batch setData:@{} forDocument:nycRef];

// Update the population of 'SF'
FIRDocumentReference *sfRef =
    [[self.db collectionWithPath:@"cities"] documentWithPath:@"SF"];
[batch updateData:@{ @"population": @1000000 } forDocument:sfRef];

// Delete the city 'LA'
FIRDocumentReference *laRef =
    [[self.db collectionWithPath:@"cities"] documentWithPath:@"LA"];
[batch deleteDocument:laRef];

// Commit the batch
[batch commitWithCompletion:^(NSError * _Nullable error) {
  if (error != nil) {
    NSLog(@"Error writing batch %@", error);
  } else {
    NSLog(@"Batch write succeeded.");
  }
}];
  
Android
// Get a new write batch
WriteBatch batch = db.batch();

// Set the value of 'NYC'
DocumentReference nycRef = db.collection("cities").document("NYC");
batch.set(nycRef, new City());

// Update the population of 'SF'
DocumentReference sfRef = db.collection("cities").document("SF");
batch.update(sfRef, "population", 1000000L);

// Delete the city 'LA'
DocumentReference laRef = db.collection("cities").document("LA");
batch.delete(laRef);

// Commit the batch
batch.commit().addOnCompleteListener(new OnCompleteListener<Void>() {
    @Override
    public void onComplete(@NonNull Task<Void> task) {
        // ...
    }
});
Java
// Get a new write batch
WriteBatch batch = db.batch();
// Set the value of 'NYC'
DocumentReference nycRef = db.collection("cities").document("NYC");
batch.set(nycRef, new City());

// Update the population of 'SF'
DocumentReference sfRef = db.collection("cities").document("SF");
batch.update(sfRef, "population", 1000000L);

// Delete the city 'LA'
DocumentReference laRef = db.collection("cities").document("LA");
batch.delete(laRef);

// asynchronously commit the batch
ApiFuture<List<WriteResult>> future = batch.commit();
// ...
// future.get() blocks on batch commit operation
for (WriteResult result :future.get()) {
  System.out.println("Update time : " + result.getUpdateTime());
}
Python
batch = db.batch()

# Set the data for NYC
nyc_ref = db.collection(u'cities').document(u'NYC')
batch.set(nyc_ref, {u'name': u'New York City'})

# Update the population for SF
sf_ref = db.collection(u'cities').document(u'SF')
batch.update(sf_ref, {u'population': 1000000})

# Delete LA
la_ref = db.collection(u'cities').document(u'LA')
batch.delete(la_ref)

# Commit the batch
batch.commit()
Node.js
// Get a new write batch
var batch = db.batch();

// Set the value of 'NYC'
var nycRef = db.collection('cities').doc('NYC');
batch.set(nycRef, { name: 'New York City' });

// Update the population of 'SF'
var sfRef = db.collection('cities').doc('SF');
batch.update(sfRef, { population: 1000000 });

// Delete the city 'LA'
var laRef = db.collection('cities').doc('LA');
batch.delete(laRef);

// Commit the batch
return batch.commit().then(function () {
    // ...
});
Go
// Get a new write batch.
batch := client.Batch()

// Set the value of "NYC".
nycRef := client.Collection("cities").Doc("NYC")
batch.Set(nycRef, map[string]interface{}{
	"name": "New York City",
})

// Update the population of "SF".
sfRef := client.Collection("cities").Doc("SF")
batch.UpdateMap(sfRef, map[string]interface{}{
	"population": 1000000,
})

// Delete the city "LA".
laRef := client.Collection("cities").Doc("LA")
batch.Delete(laRef)

// Commit the batch.
_, err := batch.Commit(ctx)
if err != nil {
	return err
}

Send feedback about...

Need help? Visit our support page.