This error occurs when you attempt to read a document after performing a write operation within a Firestore transaction, violating the fundamental constraint that all reads must happen before any writes.
This error is thrown when you violate Firestore's transaction ordering rule: all read operations must be executed before any write operations within a transaction. Firestore enforces this constraint to ensure data consistency and enable its automatic retry mechanism. When a transaction runs, Firestore needs to validate that all documents you read haven't been modified by concurrent operations before committing your writes. By requiring reads first, Firestore can detect conflicts and automatically retry the transaction with fresh data if needed. All writes execute atomically at the end of a successful transaction, never partially applying changes. This design ensures that transactions run on up-to-date and consistent data. If you attempt to call get() or getDocument() after set(), update(), or delete() operations in your transaction function, Firestore immediately throws this error to prevent inconsistent state.
Restructure your transaction so all get() calls happen first:
// ❌ WRONG: Read after write
await db.runTransaction(async (transaction) => {
const docRef = db.collection('users').doc('alice');
// Write first
transaction.update(docRef, { credits: 100 });
// Read after - THIS WILL FAIL
const doc = await transaction.get(docRef);
const balance = doc.data().balance;
});
// ✅ CORRECT: Read before write
await db.runTransaction(async (transaction) => {
const docRef = db.collection('users').doc('alice');
// Read first
const doc = await transaction.get(docRef);
const balance = doc.data().balance;
// Then write
transaction.update(docRef, { credits: balance + 100 });
});Perform all document reads at the beginning of your transaction function, then perform all writes afterward.
If your writes depend on multiple documents, read all of them upfront:
await db.runTransaction(async (transaction) => {
// Read all documents first
const userRef = db.collection('users').doc('alice');
const accountRef = db.collection('accounts').doc('12345');
const settingsRef = db.collection('settings').doc('global');
const [userDoc, accountDoc, settingsDoc] = await Promise.all([
transaction.get(userRef),
transaction.get(accountRef),
transaction.get(settingsRef)
]);
// Extract all data you need
const userData = userDoc.data();
const accountData = accountDoc.data();
const maxTransfer = settingsDoc.data().maxTransferAmount;
// Now perform all writes based on the read data
if (userData.balance >= 100 && accountData.balance + 100 <= maxTransfer) {
transaction.update(userRef, { balance: userData.balance - 100 });
transaction.update(accountRef, { balance: accountData.balance + 100 });
}
});If your operation doesn't require reading documents, use a batch instead of a transaction:
// Batched writes don't have the read-before-write restriction
const batch = db.batch();
const userRef = db.collection('users').doc('alice');
const logRef = db.collection('logs').doc();
batch.update(userRef, { lastLogin: new Date() });
batch.set(logRef, {
user: 'alice',
action: 'login',
timestamp: new Date()
});
await batch.commit();Batched writes execute atomically and can write to multiple documents without any read constraints. Use this when you only need to write data.
Ensure you're using async/await correctly so operations execute in the intended order:
// ❌ WRONG: Missing await causes race condition
await db.runTransaction(async (transaction) => {
const docRef = db.collection('items').doc('item1');
// This write might execute before the read below
transaction.set(db.collection('logs').doc(), { action: 'update' });
// Missing await here
transaction.get(docRef).then(doc => {
// This read happens after the write!
const data = doc.data();
});
});
// ✅ CORRECT: Proper await ensures ordering
await db.runTransaction(async (transaction) => {
const docRef = db.collection('items').doc('item1');
// Read with await
const doc = await transaction.get(docRef);
const data = doc.data();
// Then write
transaction.set(db.collection('logs').doc(), {
action: 'update',
previousValue: data.value
});
});Always use await on transaction.get() calls to ensure they complete before subsequent operations.
Transaction Retry Mechanism: Firestore automatically retries transactions when documents you've read are modified by concurrent operations. This is why reads must come first—the system needs to detect conflicts before applying writes. If a retry occurs, your entire transaction function runs again with fresh data.
Performance Consideration: Reading many documents in a transaction can increase the likelihood of conflicts and retries, especially in high-traffic applications. If you're experiencing frequent transaction failures due to contention, consider restructuring your data model or using batched writes when possible.
Read-Only Transactions: Some Firestore SDKs support read-only transaction modes that can improve performance for operations that only read data. These don't have the ordering restriction since they never write.
Two-Phase Operations: If you must read after a write (e.g., to verify the result), split the operation into two separate transactions or use a write followed by a separate read outside the transaction. Remember that Firestore's atomic commits ensure your transaction writes succeed completely or not at all.
messaging/UNSPECIFIED_ERROR: No additional information available
How to fix "messaging/UNSPECIFIED_ERROR: No additional information available" in Firebase Cloud Messaging
App Check: reCAPTCHA Score Too Low
App Check reCAPTCHA Score Too Low
storage/invalid-url: Invalid URL format for Cloud Storage reference
How to fix invalid URL format in Firebase Cloud Storage
auth/missing-uid: User ID identifier required
How to fix "auth/missing-uid: User ID identifier required" in Firebase
auth/invalid-argument: Invalid parameter passed to method
How to fix "auth/invalid-argument: Invalid parameter passed to method" in Firebase