3

How can I trigger a function on any document change in any collection in Firestore? I want to manage createdAt and updatedAt timestamps. I have many collections and don't want to have to register the trigger for each independently. At that point I might as well just create wrapper functions for add, set, and update.

How can I register a callback that fires when any document is modified?

EDIT:

At this time (2019-08-22), I decided to just create a wrapper function to implement this functionality. The accepted answer does not maintain schema-less-ness. Based on this article, I created this upset function which manages timestamps and avoids "document does not exist" errors:

const { firestore: { FieldValue } } = require('firebase-admin')

module.exports = async function upset (doc, data = {}) {
  const time = FieldValue.serverTimestamp()

  const update = { updatedAt: time }
  const updated = { ...data, ...update }

  try {
    const snapshot = await doc.get()

    if (snapshot.exists) {
      return doc.update(updated)
    } else {
      const create = { createdAt: time }
      const created = { ...updated, ...create }

      return doc.set(created)
    }
  } catch (error) {
    throw error
  }
}
Renaud Tarnec
  • 79,263
  • 10
  • 95
  • 121
David Y. Stephenson
  • 872
  • 4
  • 22
  • 44
  • I addition to the answer below, you may have a look at this answer https://stackoverflow.com/questions/52660962/how-to-add-timestamp-to-every-collection-insert-update-in-firebase-functions-for/52664157#52664157 (in particular the "HOWEVER" section). – Renaud Tarnec Aug 20 '19 at 10:06
  • Referenced article is now paywalled. How do you bind `upset` to every collection/subcollection in all of firestore? Why is it called `upset` instead of `upsert` (`update` + `insert`)? – Toddius Zho Sep 22 '21 at 22:06
  • To my knowledge it can not be bound. I manually call upset inside my cloud functions whenever I touch a document. The name `upset` is a combination of the two firebase function names, `update` and `set`. `update` + `set` = `upset`. – David Y. Stephenson Sep 24 '21 at 08:01

1 Answers1

8

As explained in the doc, you can use wildcards in the document path. More specifically "You may define as many wildcards as you like to substitute explicit collection or document IDs"

So, the following Cloud Function will work for documents that are under a root collection:

exports.universalFirestoreTrigger = functions.firestore
    .document('{collecId}/{docId}')
    .onWrite((snap, context) => {

        console.log("Collection: " + context.params.collecId);
        console.log("Document: " + context.params.docId);

        return null;

    });

If you have sub-collections, you will need to write another Cloud Function, as follows:

exports.universalFirestoreTriggerSubCollections = functions.firestore
    .document('{collecId}/{docId}/{subCollecId}/{subDocId}')
    .onWrite((snap, context) => {

        console.log("Collection: " + context.params.collecId);
        console.log("Document: " + context.params.docId);
        console.log("Sub-Collection: " + context.params.subCollecId);
        console.log("Sub-Collection Document: " + context.params.subDocId);

        return null;

    });

And so on if you have sub-sub-collections....

Renaud Tarnec
  • 79,263
  • 10
  • 95
  • 121
  • Thank you for your answer and comment. At the moment it seems better to just create a wrapper function in order to stay schema-less. If there is no schema-less way, I found this article that provides some examples (in TypeScript): https://angularfirebase.com/lessons/firestore-advanced-usage-angularfire/#3-CRUD-Operations-with-Server-Timestamps – David Y. Stephenson Aug 20 '19 at 10:24
  • 1
    Clearly, using `FieldValue.serverTimestamp()` is the best way to automatically maintain `createdAt` and `updatedAt` timestamps, as explained in the page you refer to (https://angularfirebase.com/lessons/firestore-advanced-usage-angularfire/#3-CRUD-Operations-with-Server-Timestamps) – Renaud Tarnec Aug 20 '19 at 10:28
  • 1
    I added an example implementation to the question. – David Y. Stephenson Aug 22 '19 at 09:23