Announcing Cloud Firestore (beta): Try the new, scalable, flexible database from Firebase and Google Cloud Platform. Learn more about Cloud Firestore.

Extend Realtime Database with Cloud Functions

With Cloud Functions, you can handle events in the Firebase Realtime Database with no need to update client code. Cloud Functions lets you run database operations with full administrative privileges, and ensures that each change to the database is processed individually. You can make Firebase Realtime Database changes via the DeltaSnapshot or via the Admin SDK.

In a typical lifecycle, a Firebase Realtime Database function does the following:

  1. Waits for changes to a particular database location.
  2. Triggers when an event occurs and performs its tasks (see What can I do with Cloud Functions? for examples of use cases).
  3. Receives an event data object that contains two snapshots of the data stored at the specified path: one with the original data prior to the change, and one with the new data.

Trigger a database function

Create new functions for Realtime Database events with functions.database. To control when the function triggers, specify one of the event handlers, and specify the database path where it will listen for events.

Set the event handler

Functions let you handle database events at two levels of specificity; you can listen for specifically for only creation, update, or deletion events, or you can listen for any change of any kind to a path. Cloud Functions supports these event handlers for Realtime Database:

  • onWrite(), which triggers when data is created, destroyed, or changed in the Realtime Database.
  • onCreate(), which triggers when new data is created in the Realtime Database.
  • onUpdate(), which triggers when data is updated in the Realtime Database.
  • onDelete(), which triggers when data is deleted from the Realtime Database.

Specify the path

To control when your function should trigger, call ref(path). This method directs your function to handle writes at a certain path within your database:

functions.database.ref('/foo/bar')

Path specifications match all writes that touch a path, including writes that happen anywhere below it. If you set the path for your function as /foo/bar, it matches events at both of these locations:

 /foo/bar
 /foo/bar/baz/really/deep/path

In either case, Firebase interprets that the event occurs at /foo/bar, and the event data includes the old and new data at /foo/bar. If the event data might be large, consider using multiple functions at deeper paths instead of a single function near the root of your database. For the best performance, only request data at the deepest level possible.

You can specify a path component as a wildcard by surrounding it with curly brackets; ref('foo/{bar}') matches any child of /foo. The values of these wildcard path components are available within the event.params object of your function. In this example, the value is available as event.params.bar.

Paths with wildcards can match multiple events from a single write. An insert of

{
  "foo": {
    "hello": "world",
    "firebase": "functions"
  }
}

matches the path "/foo/{bar}" twice: once with "hello": "world" and again with "firebase": "functions".

Handle event data

When handling a Realtime Database event, event.data is a DeltaSnapshot. In this example, the function retrieves event.data for the specified path, converts the string at that location to uppercase, and writes that modified string to the location /messages/{pushId}/uppercased:

// Listens for new messages added to /messages/:pushId/original and creates an
// uppercase version of the message to /messages/:pushId/uppercase
exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
    .onWrite(event => {
      // Grab the current value of what was written to the Realtime Database.
      const original = event.data.val();
      console.log('Uppercasing', event.params.pushId, original);
      const uppercase = original.toUpperCase();
      // You must return a Promise when performing asynchronous tasks inside a Functions such as
      // writing to the Firebase Realtime Database.
      // Setting an "uppercase" sibling in the Realtime Database returns a Promise.
      return event.data.ref.parent.child('uppercase').set(uppercase);
    });

Reading the previous value

The DeltaSnapshot has a previous property that lets you inspect what was saved to the database before the event. The previous property returns a new DeltaSnapshot where all methods (for example, val() and exists()) refer to the previous value. You can read the new value again by either using the original DeltaSnapshot or reading the current property on any DeltaSnapshot to read a value as it is after the event.

For example, the previous property could be used to make the makeUppercase() function an uppercase-only new value:

exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
    .onWrite(event => {
      // Only edit data when it is first created.
      if (event.data.previous.exists()) {
        return null;
      }
      // Exit when the data is deleted.
      if (!event.data.exists()) {
        return null;
      }
      // Grab the current value of what was written to the Realtime Database.
      const original = event.data.val();
      console.log('Uppercasing', event.params.pushId, original);
      const uppercase = original.toUpperCase();
      // You must return a Promise when performing asynchronous tasks inside a Functions such as
      // writing to the Firebase Realtime Database.
      // Setting an "uppercase" sibling in the Realtime Database returns a Promise.
      return event.data.ref.parent.child('uppercase').set(uppercase);
    });

Monitoring changed values

Sometimes you don't need the old value; you just need to know whether data has changed. You can see if data changed at a path using the changed() function on DeltaSnapshot. This function only calls createThumbnail() if a change to a user profile also changed the value of profilePicture:

exports.thumbnailProfile = functions.database.ref('/profiles/{userID}')
  .onWrite(event => {
    var eventSnapshot = event.data;
    var profilePictureSnapshot = eventSnapshot.child('profilePicture');
    if (profilePictureSnapshot.changed()) {
      return createThumbnail(profilePictureSnapshot.val())
        .then(url => {
          return eventSnapshot.ref.update({ profileThumbnail: url });
        });
    }
  });