You can trigger Cloud Functions on Firestore document changes, but the surprising truth is that the trigger isn’t just for changes—it fires on any modification to the document, including creating, updating, and deleting.

Let’s see this in action. Imagine you have a Firestore collection called users and you want to log every time a user’s profile is updated.

// index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();

exports.logUserUpdate = functions.firestore
    .document('users/{userId}')
    .onUpdate((change, context) => {
        const newValue = change.after.data();
        const oldValue = change.before.data();

        console.log(`User ${context.params.userId} updated. Old data:`, oldValue);
        console.log(`User ${context.params.userId} updated. New data:`, newValue);

        // You could also write to another document, send an email, etc.
        return Promise.resolve();
    });

When you deploy this function and then, say, change a user’s displayName in your Firestore console for a document with ID alice123, the onUpdate function fires. The change object contains two snapshots: change.before (the document’s state before the update) and change.after (the document’s state after the update). context.params.userId will be alice123.

This mechanism is incredibly powerful for building reactive applications. It abstracts away the polling or complex listening logic you’d otherwise need. Instead of constantly asking Firestore "has anything changed?", Firestore tells your function when something has changed. The onWrite trigger is even more general, firing for create, update, and delete, while onCreate and onDelete are specific.

The core problem this solves is event-driven data synchronization and automation. You want to react to data changes without writing boilerplate code to detect those changes. Cloud Functions, when coupled with Firestore triggers, provide a serverless, event-driven backend that’s automatically scaled by Google Cloud.

Internally, Firestore triggers work by leveraging Google Cloud’s Pub/Sub infrastructure. When a document in your Firestore database is modified, Firestore publishes an event to a dedicated Pub/Sub topic. Cloud Functions then subscribe to these topics. When an event arrives, Cloud Functions invokes your deployed function, passing the event data (the document snapshots and context) as arguments.

Consider the context object passed to your function. It contains not just parameters extracted from the document path (like userId in our example), but also metadata such as the event ID, timestamp, and resource details. This context is crucial for understanding when and where the event originated, enabling more sophisticated logic, like preventing infinite loops by checking the event timestamp against known processing times.

The most common way to structure your triggers is by using wildcards in the document path, like users/{userId}. This allows a single function to handle changes for any document within that collection. You can also nest wildcards, for example, users/{userId}/posts/{postId}, to trigger functions based on changes within subcollections.

When you’re dealing with a high volume of writes, especially if your function performs complex operations or writes back to Firestore, you need to be mindful of potential infinite loops. If your function modifies the same document that triggered it, it will fire again, leading to a recursive call. A common pattern to avoid this is to check if specific fields have changed or to write to a different document or collection. For instance, you could add a lastProcessedTimestamp field to the document and check if the change.after.data().lastProcessedTimestamp is older than the current event’s timestamp before proceeding.

The next logical step after mastering document change triggers is to explore the capabilities of Firestore’s Security Rules, which are indispensable for controlling data access in real-time.

Want structured learning?

Take the full Cloud-functions course →