Firebase Realtime Database Triggers

With Cloud Run functions, you can handle events in the Firebase Realtime Database in the same Google Cloud project as the function. Cloud Run 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 Firebase 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.

  3. Receives a data object that contains a snapshot of the data stored in the specified document.

Event types

Functions lets you handle database events at two levels of specificity; you can listen specifically for only creation, update, or deletion events, or you can listen for any change of any kind to a path. Cloud Run functions supports the following event types for the Realtime Database:

Event Type Trigger
providers/google.firebase.database/eventTypes/ref.write Triggered on any mutation event: when data is created, updated, or deleted in the Realtime Database.
providers/google.firebase.database/eventTypes/ref.create (default) Triggered when new data is created in the Realtime Database.
providers/google.firebase.database/eventTypes/ref.update Triggered when data is updated in the Realtime Database.
providers/google.firebase.database/eventTypes/ref.delete Triggered when data is deleted from the Realtime Database.

Specifying the database path and instance

To control when and where your function should trigger, you need to specify a path, and optionally specify a database instance.

Path

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 braces; 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".

Instance

When using the Google Cloud console, the database instance must be specified.

When using the Google Cloud CLI, the instance must be specified as part of the --trigger-resource string.

For example the following would use the following in your --trigger-resource string:

--trigger-resource projects/_/instances/DATABASE_INSTANCE/refs/PATH

Event structure

When handling a Realtime Database event, the data object contains two properties that are provided in JSON object format:

  • data: a snapshot of the data taken prior to the event that triggered the function.

  • delta: a snapshot of the data taken after the event that triggered the function.

Code sample

Node.js

/**
 * Background Function triggered by a change to a Firebase RTDB reference.
 *
 * @param {!Object} event The Cloud Functions event.
 * @param {!Object} context The Cloud Functions event context.
 */
exports.helloRTDB = (event, context) => {
  const triggerResource = context.resource;

  console.log(`Function triggered by change to: ${triggerResource}`);
  console.log(`Admin?: ${!!context.auth.admin}`);
  console.log('Delta:');
  console.log(JSON.stringify(event.delta, null, 2));
};

Python

import json

def hello_rtdb(data, context):
    """Triggered by a change to a Firebase RTDB reference.
    Args:
        data (dict): The event payload.
        context (google.cloud.functions.Context): Metadata for the event.
    """
    trigger_resource = context.resource

    print("Function triggered by change to: %s" % trigger_resource)
    print("Admin?: %s" % data.get("admin", False))
    print("Delta:")
    print(json.dumps(data["delta"]))

Go


// Package p contains a Cloud Function triggered by a Firebase Realtime Database
// event.
package p

import (
	"context"
	"fmt"
	"log"

	"cloud.google.com/go/functions/metadata"
)

// RTDBEvent is the payload of a RTDB event.
type RTDBEvent struct {
	Data  interface{} `json:"data"`
	Delta interface{} `json:"delta"`
}

// HelloRTDB handles changes to a Firebase RTDB.
func HelloRTDB(ctx context.Context, e RTDBEvent) error {
	meta, err := metadata.FromContext(ctx)
	if err != nil {
		return fmt.Errorf("metadata.FromContext: %w", err)
	}
	log.Printf("Function triggered by change to: %v", meta.Resource)
	log.Printf("%+v", e)
	return nil
}

Java

import com.google.cloud.functions.Context;
import com.google.cloud.functions.RawBackgroundFunction;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.util.logging.Logger;

public class FirebaseRtdb implements RawBackgroundFunction {
  private static final Logger logger = Logger.getLogger(FirebaseRtdb.class.getName());

  // Use GSON (https://github.com/google/gson) to parse JSON content.
  private static final Gson gson = new Gson();

  @Override
  public void accept(String json, Context context) {
    logger.info("Function triggered by change to: " + context.resource());

    JsonObject body = gson.fromJson(json, JsonObject.class);

    boolean isAdmin = false;
    if (body != null && body.has("auth")) {
      JsonObject authObj = body.getAsJsonObject("auth");
      isAdmin = authObj.has("admin") && authObj.get("admin").getAsBoolean();
    }

    logger.info("Admin?: " + isAdmin);

    if (body != null && body.has("delta")) {
      logger.info("Delta:");
      logger.info(body.get("delta").toString());
    }
  }
}

C#

using CloudNative.CloudEvents;
using Google.Cloud.Functions.Framework;
using Google.Events.Protobuf.Firebase.Database.V1;
using Microsoft.Extensions.Logging;
using System.Threading;
using System.Threading.Tasks;

namespace FirebaseRtdb;

public class Function : ICloudEventFunction<ReferenceEventData>
{
    private readonly ILogger _logger;

    public Function(ILogger<Function> logger) =>
        _logger = logger;

    public Task HandleAsync(CloudEvent cloudEvent, ReferenceEventData data, CancellationToken cancellationToken)
    {
        _logger.LogInformation("Function triggered by change to {subject}", cloudEvent.Subject);
        _logger.LogInformation("Delta: {delta}", data.Delta);

        // In this example, we don't need to perform any asynchronous operations, so the
        // method doesn't need to be declared async.
        return Task.CompletedTask;
    }
}

Ruby

require "functions_framework"

# Triggered by a change to a Firebase RTDB document.
FunctionsFramework.cloud_event "hello_rtdb" do |event|
  # Event-triggered Ruby functions receive a CloudEvents::Event::V1 object.
  # See https://cloudevents.github.io/sdk-ruby/latest/CloudEvents/Event/V1.html
  # The Firebase event payload can be obtained from the `data` field.
  payload = event.data

  logger.info "Function triggered by change to: #{event.source}"
  logger.info "Admin?: #{payload.fetch 'admin', false}"
  logger.info "Delta: #{payload['delta']}"
end

PHP


use Google\CloudFunctions\CloudEvent;

function firebaseRTDB(CloudEvent $cloudevent)
{
    $log = fopen(getenv('LOGGER_OUTPUT') ?: 'php://stderr', 'wb');

    fwrite($log, 'Event: ' . $cloudevent->getId() . PHP_EOL);

    $data = $cloudevent->getData();
    $resource = $data['resource'] ?? '<null>';

    fwrite($log, 'Function triggered by change to: ' . $resource . PHP_EOL);

    $isAdmin = isset($data['auth']['admin']) && $data['auth']['admin'] == true;

    fwrite($log, 'Admin?: ' . var_export($isAdmin, true) . PHP_EOL);
    fwrite($log, 'Delta: ' . json_encode($data['delta'] ?? '') . PHP_EOL);
}

Deploying your function

The following gcloud command deploys a function that will be triggered by create events on the path /messages/{pushId}/original:

gcloud functions deploy FUNCTION_NAME \
  --no-gen2 \
  --entry-point ENTRY_POINT \
  --trigger-event providers/google.firebase.database/eventTypes/ref.create \
  --trigger-resource projects/_/instances/DATABASE_INSTANCE/refs/messages/{pushId}/original \
  --runtime RUNTIME
Argument Description
FUNCTION_NAME The registered name of the Cloud Run function you are deploying. This can either be the name of a function in your source code, or an arbitrary string. If FUNCTION_NAME is an arbitrary string, then you must include the --entry-point flag.
--entry-point ENTRY_POINT The name of a function or class in your source code. Optional, unless you did not use FUNCTION_NAME to specify the function in your source code to be executed during deployment. In that case, you must use --entry-point to supply the name of the executable function.
--trigger-event NAME The name of the event type that the function wishes to receive. In this case, it will be one of the following: write, create, update or delete.
--trigger-resource NAME The fully qualified database path to which the function will listen. This should conform to the following format: projects/_/instances/DATABASE_INSTANCE/refs/PATH.
--runtime RUNTIME The name of the runtime you are using. For a complete list, see the gcloud reference.