Extend with Cloud Run functions (2nd gen)

With Cloud Run functions, you can deploy code to handle events triggered by changes in your Firestore database. This allows you to add server-side functionality without running your own servers.

Extend Firestore with Cloud Run functions (2nd gen)

Cloud Run functions (2nd gen) supports the following Firestore event triggers to let you create handlers tied to Firestore events:

Event Type Trigger
google.cloud.firestore.document.v1.created Triggered when a document is written to for the first time.
google.cloud.firestore.document.v1.updated Triggered when a document already exists and has any value changed.
google.cloud.firestore.document.v1.deleted Triggered when a document is deleted.
google.cloud.firestore.document.v1.written Triggered when created, updated or deleted is triggered.
google.cloud.firestore.document.v1.created.withAuthContext Same as created but adds authentication information.
google.cloud.firestore.document.v1.updated.withAuthContext Same as updated but adds authentication information.
google.cloud.firestore.document.v1.deleted.withAuthContext Same as deleted but adds authentication information.
google.cloud.firestore.document.v1.written.withAuthContext Same as written but adds authentication information.

Firestore events trigger only on document changes. An update to a Firestore document where data is unchanged (a no-op write) does not generate an update or write event. It is not possible to add events to specific fields.

Include authentication context in the event

To include additional authentication information about the event, use an event trigger with the withAuthContext extension. This extension adds additional information about the principal that triggered the event. It adds the authtype and authid attributes in addition to the information returned in the base event. See the authcontext reference for more information about attribute values.

Write a Firestore-triggered function

To write a function that responds to Firestore events, prepare to specify following during deployment:

  • a trigger event type
  • a trigger event filter to select the documents associated with the function
  • the function code to run

Trigger event filters

When you specify an event filter, you can specify either an exact document match or a path pattern. Use a path pattern to match multiple documents with wildcards, * or **.

For example, you can respond to changes to the following document:

users/marie

Use wildcards, * or **, to respond to changes in documents that match a pattern. A wildcard * matches a single segment and the a multi-segment wildcard ** matches zero or more segments in the pattern.

For single segment matches (*) you can also use a named capture group. For example, users/{userId}.

For example:

Pattern Description
/users/* or /users/{userId} Matches all documents in the /users collection. Does not match documents in sub-collections like /users/marie/messages/33e2IxYBD9enzS50SJ68
/users/** Matches all documents in the /users collection and documents in subcollections like /users/marie/messages/33e2IxYBD9enzS50SJ68

To learn more about path patterns, see Eventarc path patterns.

Your trigger must always point to a document, even if you're using a wildcard. For example, users/{userId=*}/{messageCollectionId=*} is not valid because {messageCollectionId=*} is a collection. However, users/{userId=*}/{messageCollectionId}/{messageId=*} is valid because {messageId=*} will always point to a document.

Example functions

The following sample demonstrates how to receive Firestore events. To work with the document data involved in an event, look at the value and old_value fields.

  • value: A Document object that contains a post-operation document snapshot. This field is not populated for delete events.
  • old_value: A Document object that contains a pre-operation document snapshot. This field is only populated for update and delete events.

Go

To authenticate to Firestore, set up Application Default Credentials. For more information, see Set up authentication for a local development environment.


// Package hellofirestore contains a Cloud Event Function triggered by a Cloud Firestore event.
package hellofirestore

import (
	"context"
	"fmt"

	"github.com/GoogleCloudPlatform/functions-framework-go/functions"
	"github.com/cloudevents/sdk-go/v2/event"
	"github.com/googleapis/google-cloudevents-go/cloud/firestoredata"
	"google.golang.org/protobuf/proto"
)

func init() {
	functions.CloudEvent("helloFirestore", HelloFirestore)
}

// HelloFirestore is triggered by a change to a Firestore document.
func HelloFirestore(ctx context.Context, event event.Event) error {
	var data firestoredata.DocumentEventData

	// If you omit `DiscardUnknown`, protojson.Unmarshal returns an error
	// when encountering a new or unknown field.
	options := proto.UnmarshalOptions{
		DiscardUnknown: true,
	}
	err := options.Unmarshal(event.Data(), &data)

	if err != nil {
		return fmt.Errorf("proto.Unmarshal: %w", err)
	}

	fmt.Printf("Function triggered by change to: %v\n", event.Source())
	fmt.Printf("Old value: %+v\n", data.GetOldValue())
	fmt.Printf("New value: %+v\n", data.GetValue())
	return nil
}

Java

To authenticate to Firestore, set up Application Default Credentials. For more information, see Set up authentication for a local development environment.

import com.google.cloud.functions.CloudEventsFunction;
import com.google.events.cloud.firestore.v1.DocumentEventData;
import com.google.protobuf.InvalidProtocolBufferException;
import io.cloudevents.CloudEvent;
import java.util.logging.Logger;

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

  @Override
  public void accept(CloudEvent event) throws InvalidProtocolBufferException {
    DocumentEventData firestoreEventData = DocumentEventData
        .parseFrom(event.getData().toBytes());

    logger.info("Function triggered by event on: " + event.getSource());
    logger.info("Event type: " + event.getType());

    logger.info("Old value:");
    logger.info(firestoreEventData.getOldValue().toString());

    logger.info("New value:");
    logger.info(firestoreEventData.getValue().toString());
  }
}

Node.js

To authenticate to Firestore, set up Application Default Credentials. For more information, see Set up authentication for a local development environment.

Use protobufjs to decode the event data. Include the google.events.cloud.firestore.v1 data.proto in your source.
/**
 * Cloud Event Function triggered by a change to a Firestore document.
 */
const functions = require('@google-cloud/functions-framework');
const protobuf = require('protobufjs');

functions.cloudEvent('helloFirestore', async cloudEvent => {
  console.log(`Function triggered by event on: ${cloudEvent.source}`);
  console.log(`Event type: ${cloudEvent.type}`);

  console.log('Loading protos...');
  const root = await protobuf.load('data.proto');
  const DocumentEventData = root.lookupType(
    'google.events.cloud.firestore.v1.DocumentEventData'
  );

  console.log('Decoding data...');
  const firestoreReceived = DocumentEventData.decode(cloudEvent.data);

  console.log('\nOld value:');
  console.log(JSON.stringify(firestoreReceived.oldValue, null, 2));

  console.log('\nNew value:');
  console.log(JSON.stringify(firestoreReceived.value, null, 2));
});

Python

To authenticate to Firestore, set up Application Default Credentials. For more information, see Set up authentication for a local development environment.

from cloudevents.http import CloudEvent
import functions_framework
from google.events.cloud import firestore


@functions_framework.cloud_event
def hello_firestore(cloud_event: CloudEvent) -> None:
    """Triggers by a change to a Firestore document.

    Args:
        cloud_event: cloud event with information on the firestore event trigger
    """
    firestore_payload = firestore.DocumentEventData()
    firestore_payload._pb.ParseFromString(cloud_event.data)

    print(f"Function triggered by change to: {cloud_event['source']}")

    print("\nOld value:")
    print(firestore_payload.old_value)

    print("\nNew value:")
    print(firestore_payload.value)

C#

To authenticate to Firestore, set up Application Default Credentials. For more information, see Set up authentication for a local development environment.

using CloudNative.CloudEvents;
using Google.Cloud.Functions.Framework;
using Google.Events.Protobuf.Cloud.Firestore.V1;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace FirebaseFirestore;

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

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

    public Task HandleAsync(CloudEvent cloudEvent, DocumentEventData data, CancellationToken cancellationToken)
    {
        _logger.LogInformation("Function triggered by event on {subject}", cloudEvent.Subject);
        _logger.LogInformation("Event type: {type}", cloudEvent.Type);
        MaybeLogDocument("Old value", data.OldValue);
        MaybeLogDocument("New value", data.Value);

        // 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;
    }

    /// <summary>
    /// Logs the names and values of the fields in a document in a very simplistic way.
    /// </summary>
    private void MaybeLogDocument(string message, Document document)
    {
        if (document is null)
        {
            return;
        }

        // ConvertFields converts the Firestore representation into a .NET-friendly
        // representation.
        IReadOnlyDictionary<string, object> fields = document.ConvertFields();
        var fieldNamesAndTypes = fields
            .OrderBy(pair => pair.Key)
            .Select(pair => $"{pair.Key}: {pair.Value}");
        _logger.LogInformation(message + ": {fields}", string.Join(", ", fieldNamesAndTypes));
    }
}

The following examples converts strings added to the original field of an affected doc to uppercase and writes the new value to the same document:

Go

To authenticate to Firestore, set up Application Default Credentials. For more information, see Set up authentication for a local development environment.


// Package upper contains a Firestore Cloud Function.
package upper

import (
	"context"
	"errors"
	"fmt"
	"log"
	"os"
	"strings"

	"cloud.google.com/go/firestore"
	firebase "firebase.google.com/go/v4"
	"github.com/GoogleCloudPlatform/functions-framework-go/functions"
	"github.com/cloudevents/sdk-go/v2/event"
	"github.com/googleapis/google-cloudevents-go/cloud/firestoredata"
	"google.golang.org/protobuf/proto"
)

// set the GOOGLE_CLOUD_PROJECT environment variable when deploying.
var projectID = os.Getenv("GOOGLE_CLOUD_PROJECT")

// client is a Firestore client, reused between function invocations.
var client *firestore.Client

func init() {
	// Use the application default credentials.
	conf := &firebase.Config{ProjectID: projectID}

	// Use context.Background() because the app/client should persist across
	// invocations.
	ctx := context.Background()

	app, err := firebase.NewApp(ctx, conf)
	if err != nil {
		log.Fatalf("firebase.NewApp: %v", err)
	}

	client, err = app.Firestore(ctx)
	if err != nil {
		log.Fatalf("app.Firestore: %v", err)
	}

	// Register cloud event function
	functions.CloudEvent("MakeUpperCase", MakeUpperCase)
}

// MakeUpperCase is triggered by a change to a Firestore document. It updates
// the `original` value of the document to upper case.
func MakeUpperCase(ctx context.Context, e event.Event) error {
	var data firestoredata.DocumentEventData

	// If you omit `DiscardUnknown`, protojson.Unmarshal returns an error
	// when encountering a new or unknown field.
	options := proto.UnmarshalOptions{
		DiscardUnknown: true,
	}
	err := options.Unmarshal(e.Data(), &data)

	if err != nil {
		return fmt.Errorf("proto.Unmarshal: %w", err)
	}

	if data.GetValue() == nil {
		return errors.New("Invalid message: 'Value' not present")
	}

	fullPath := strings.Split(data.GetValue().GetName(), "/documents/")[1]
	pathParts := strings.Split(fullPath, "/")
	collection := pathParts[0]
	doc := strings.Join(pathParts[1:], "/")

	var originalStringValue string
	if v, ok := data.GetValue().GetFields()["original"]; ok {
		originalStringValue = v.GetStringValue()
	} else {
		return errors.New("Document did not contain field \"original\"")
	}

	newValue := strings.ToUpper(originalStringValue)
	if originalStringValue == newValue {
		log.Printf("%q is already upper case: skipping", originalStringValue)
		return nil
	}
	log.Printf("Replacing value: %q -> %q", originalStringValue, newValue)

	newDocumentEntry := map[string]string{"original": newValue}
	_, err = client.Collection(collection).Doc(doc).Set(ctx, newDocumentEntry)
	if err != nil {
		return fmt.Errorf("Set: %w", err)
	}
	return nil
}

Java

To authenticate to Firestore, set up Application Default Credentials. For more information, see Set up authentication for a local development environment.

import com.google.cloud.firestore.Firestore;
import com.google.cloud.firestore.FirestoreOptions;
import com.google.cloud.firestore.SetOptions;
import com.google.cloud.functions.CloudEventsFunction;
import com.google.events.cloud.firestore.v1.DocumentEventData;
import com.google.events.cloud.firestore.v1.Value;
import com.google.protobuf.InvalidProtocolBufferException;
import io.cloudevents.CloudEvent;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.logging.Logger;

public class FirebaseFirestoreReactive implements CloudEventsFunction {
  private static final Logger logger = Logger.getLogger(FirebaseFirestoreReactive.class.getName());
  private final Firestore firestore;

  private static final String FIELD_KEY = "original";
  private static final String APPLICATION_PROTOBUF = "application/protobuf";

  public FirebaseFirestoreReactive() {
    this(FirestoreOptions.getDefaultInstance().getService());
  }

  public FirebaseFirestoreReactive(Firestore firestore) {
    this.firestore = firestore;
  }

  @Override
  public void accept(CloudEvent event)
      throws InvalidProtocolBufferException, InterruptedException, ExecutionException {
    if (event.getData() == null) {
      logger.warning("No data found in event!");
      return;
    }

    if (!event.getDataContentType().equals(APPLICATION_PROTOBUF)) {
      logger.warning(String.format("Found unexpected content type %s, expected %s",
          event.getDataContentType(),
          APPLICATION_PROTOBUF));
      return;
    }

    DocumentEventData firestoreEventData = DocumentEventData
        .parseFrom(event.getData().toBytes());

    // Get the fields from the post-operation document snapshot
    // https://firebase.google.com/docs/firestore/reference/rest/v1/projects.databases.documents#Document
    Map<String, Value> fields = firestoreEventData.getValue().getFieldsMap();
    if (!fields.containsKey(FIELD_KEY)) {
      logger.warning("Document does not contain original field");
      return;
    }
    String currValue = fields.get(FIELD_KEY).getStringValue();
    String newValue = currValue.toUpperCase();

    if (currValue.equals(newValue)) {
      logger.info("Value is already upper-case");
      return;
    }

    // Retrieve the document name from the resource path:
    // projects/{project_id}/databases/{database_id}/documents/{document_path}
    String affectedDoc = firestoreEventData.getValue()
        .getName()
        .split("/documents/")[1]
        .replace("\"", "");

    logger.info(String.format("Replacing values: %s --> %s", currValue, newValue));

    // Wait for the async call to complete
    this.firestore
        .document(affectedDoc)
        .set(Map.of(FIELD_KEY, newValue), SetOptions.merge())
        .get();
  }
}

Node.js

To authenticate to Firestore, set up Application Default Credentials. For more information, see Set up authentication for a local development environment.

Use protobufjs to decode the event data. Include the google.events.cloud.firestore.v1 data.proto in your source.
const functions = require('@google-cloud/functions-framework');
const Firestore = require('@google-cloud/firestore');
const protobuf = require('protobufjs');

const firestore = new Firestore({
  projectId: process.env.GOOGLE_CLOUD_PROJECT,
});

// Converts strings added to /messages/{pushId}/original to uppercase
functions.cloudEvent('makeUpperCase', async cloudEvent => {
  console.log('Loading protos...');
  const root = await protobuf.load('data.proto');
  const DocumentEventData = root.lookupType(
    'google.events.cloud.firestore.v1.DocumentEventData'
  );

  console.log('Decoding data...');
  const firestoreReceived = DocumentEventData.decode(cloudEvent.data);

  const resource = firestoreReceived.value.name;
  const affectedDoc = firestore.doc(resource.split('/documents/')[1]);

  const curValue = firestoreReceived.value.fields.original.stringValue;
  const newValue = curValue.toUpperCase();

  if (curValue === newValue) {
    // Value is already upper-case
    // Don't perform a(nother) write to avoid infinite loops
    console.log('Value is already upper-case.');
    return;
  }

  console.log(`Replacing value: ${curValue} --> ${newValue}`);
  affectedDoc.set({
    original: newValue,
  });
});

Python

To authenticate to Firestore, set up Application Default Credentials. For more information, see Set up authentication for a local development environment.

from cloudevents.http import CloudEvent
import functions_framework
from google.cloud import firestore
from google.events.cloud import firestore as firestoredata

client = firestore.Client()


# Converts strings added to /messages/{pushId}/original to uppercase
@functions_framework.cloud_event
def make_upper_case(cloud_event: CloudEvent) -> None:
    firestore_payload = firestoredata.DocumentEventData()
    firestore_payload._pb.ParseFromString(cloud_event.data)

    path_parts = firestore_payload.value.name.split("/")
    separator_idx = path_parts.index("documents")
    collection_path = path_parts[separator_idx + 1]
    document_path = "/".join(path_parts[(separator_idx + 2) :])

    print(f"Collection path: {collection_path}")
    print(f"Document path: {document_path}")

    affected_doc = client.collection(collection_path).document(document_path)

    cur_value = firestore_payload.value.fields["original"].string_value
    new_value = cur_value.upper()

    if cur_value != new_value:
        print(f"Replacing value: {cur_value} --> {new_value}")
        affected_doc.set({"original": new_value})
    else:
        # Value is already upper-case
        # Don't perform a second write (which can trigger an infinite loop)
        print("Value is already upper-case.")

C#

To authenticate to Firestore, set up Application Default Credentials. For more information, see Set up authentication for a local development environment.

using CloudNative.CloudEvents;
using Google.Cloud.Firestore;
using Google.Cloud.Functions.Framework;
using Google.Cloud.Functions.Hosting;
using Google.Events.Protobuf.Cloud.Firestore.V1;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace FirestoreReactive;

public class Startup : FunctionsStartup
{
    public override void ConfigureServices(WebHostBuilderContext context, IServiceCollection services) =>
        services.AddSingleton(FirestoreDb.Create());
}

// Register the startup class to provide the Firestore dependency.
[FunctionsStartup(typeof(Startup))]
public class Function : ICloudEventFunction<DocumentEventData>
{
    private readonly ILogger _logger;
    private readonly FirestoreDb _firestoreDb;

    public Function(ILogger<Function> logger, FirestoreDb firestoreDb) =>
        (_logger, _firestoreDb) = (logger, firestoreDb);

    public async Task HandleAsync(CloudEvent cloudEvent, DocumentEventData data, CancellationToken cancellationToken)
    {
        // Get the recently-written value. This expression will result in a null value
        // if any of the following is true:
        // - The event doesn't contain a "new" document
        // - The value doesn't contain a field called "original"
        // - The "original" field isn't a string
        string currentValue = data.Value?.ConvertFields().GetValueOrDefault("original") as string;
        if (currentValue is null)
        {
            _logger.LogWarning($"Event did not contain a suitable document");
            return;
        }

        string newValue = currentValue.ToUpperInvariant();
        if (newValue == currentValue)
        {
            _logger.LogInformation("Value is already upper-cased; no replacement necessary");
            return;
        }

        // The CloudEvent subject is "documents/x/y/...".
        // The Firestore SDK FirestoreDb.Document method expects a reference relative to
        // "documents" (so just the "x/y/..." part). This may be simplified over time.
        if (cloudEvent.Subject is null || !cloudEvent.Subject.StartsWith("documents/"))
        {
            _logger.LogWarning("CloudEvent subject is not a document reference.");
            return;
        }
        string documentPath = cloudEvent.Subject.Substring("documents/".Length);

        _logger.LogInformation("Replacing '{current}' with '{new}' in '{path}'", currentValue, newValue, documentPath);
        await _firestoreDb.Document(documentPath).UpdateAsync("original", newValue, cancellationToken: cancellationToken);
    }
}

Include the proto dependencies in your source

You must include the Firestore data.proto file in the source directory for your function. This file imports the following protos which you must also include in your source directory:

Use the same directory structure for the dependencies. For example, place struct.proto within google/protobuf.

These files are required to decode event data. If your function source does not include these files, it returns an error when it runs.

Event attributes

Each event includes data attributes that include information about the event such as the time the event triggered. Firestore adds additional data about the database and document involved in the event. You can access these attributes as follows:

Java
logger.info("Function triggered by event on: " + event.getSource());
logger.info("Event type: " + event.getType());
logger.info("Event time " + event.getTime());
logger.info("Event project: " + event.getExtension("project"));
logger.info("Event location: " + event.getExtension("location"));
logger.info("Database name: " + event.getExtension("database"));
logger.info("Database document: " + event.getExtension("document"));
// For withAuthContext events
logger.info("Auth information: " + event.getExtension("authid"));
logger.info("Auth information: " + event.getExtension("authtype"));
Node.js
console.log(`Function triggered by event on: ${cloudEvent.source}`);
console.log(`Event type: ${cloudEvent.type}`);
console.log(`Event time: ${cloudEvent.time}`);
console.log(`Event project: ${cloudEvent.project}`);
console.log(`Event location: ${cloudEvent.location}`);
console.log(`Database name: ${cloudEvent.database}`);
console.log(`Document name: ${cloudEvent.document}`);
// For withAuthContext events
console.log(`Auth information: ${cloudEvent.authid}`);
console.log(`Auth information: ${cloudEvent.authtype}`);
Python
print(f"Function triggered by change to: {cloud_event['source']}")
print(f"Event type: {cloud_event['type']}")
print(f"Event time: {cloud_event['time']}")
print(f"Event project: {cloud_event['project']}")
print(f"Location: {cloud_event['location']}")
print(f"Database name: {cloud_event['database']}")
print(f"Document: {cloud_event['document']}")
// For withAuthContext events
print(f"Auth information: {cloud_event['authid']}")
print(f"Auth information: {cloud_event['authtype']}")

Deploy a function

Users deploying Cloud Run functions must have the Cloud Run functions Developer IAM role or a role that includes the same permissions. See also Additional configuration for deployment.

You can deploy a function using either the gcloud CLI or the Google Cloud console. The example below demonstrates deployment with the gcloud CLI. For details on deployment with the Google Cloud console, see Deploy Cloud Run functions

gcloud

  1. In the Google Cloud console, activate Cloud Shell.

    Activate Cloud Shell

    At the bottom of the Google Cloud console, a Cloud Shell session starts and displays a command-line prompt. Cloud Shell is a shell environment with the Google Cloud CLI already installed and with values already set for your current project. It can take a few seconds for the session to initialize.

  2. Use the gcloud functions deploy command to deploy a function:

    gcloud functions deploy YOUR_FUNCTION_NAME \
    --gen2 \
    --region=FUNCTION_LOCATION \
    --trigger-location=TRIGGER_LOCATION \
    --runtime=YOUR_RUNTIME \
    --source=YOUR_SOURCE_LOCATION \
    --entry-point=YOUR_CODE_ENTRYPOINT \
    --trigger-event-filters="type=EVENT_FILTER_TYPE" \
    --trigger-event-filters="database=DATABASE" \
    --trigger-event-filters-path-pattern="document=DOCUMENT" \
    

    The first argument, YOUR_FUNCTION_NAME, is a name for your deployed function. The function name must start with a letter followed by up to 62 letters, numbers, hyphens, or underscores, and must end with a letter or a number.

    • The --gen2 flag specifies that you want to deploy to Cloud Run functions (2nd gen). Omitting this flag results in deployment to Cloud Run functions (1st gen).

    • The --region flag specifies the region in which to deploy your function.

      To maximize proximity, set to a region near your Firestore database. If your Firestore database is in a multi-region location, set to us-central1 for databases innam5 and to europe-west4 for databases in eur3. For regional Firestore locations, set to the same region.

    • The --trigger-location flag specifies the location of the trigger. You must set this flag to the location of your Firestore database.

    • The --runtime flag specifies which language runtime your function uses. Cloud Run functions supports several runtimes - see Runtimes for more information.

    • The --source flag specifies the location of your function source code. See the following for details:

    • The --entry-point flag specifies the entry point to your function in your source code. This is the code that will be executed when your function runs. The value of this flag must be a function name or fully-qualified class name that exists in your source code. See Function entry point for more information.

    • EVENT_FILTER_TYPE: Firestore supports the following event types.

      • google.cloud.firestore.document.v1.created: event is sent when a document is written to for the first time.
      • google.cloud.firestore.document.v1.updated: event is sent when a document already exists and has any value changed.
      • google.cloud.firestore.document.v1.deleted: event is sent when a document is deleted.
      • google.cloud.firestore.document.v1.written: event is sent when a document is created, updated, or deleted.
      • google.cloud.firestore.document.v1.created.withAuthContext: event is sent when a document is written to for the first time and the event includes additional authentication information
      • google.cloud.firestore.document.v1.updated.withAuthContext: event is sent when a document already exists and has any value changed. Includes additional authentication information
      • google.cloud.firestore.document.v1.deleted.withAuthContext: event is sent when a document is deleted. Includes additional authentication information
      • google.cloud.firestore.document.v1.written.withAuthContext: event is sent when a document is created, updated, or deleted and event. Includes additional authentication information
    • DATABASE: the Firestore database. For the default database name, use (default).

    • DOCUMENT: the database path that triggers events when data is created, updated, or deleted. The operator can be one of the following:

      • Equal; for example, --trigger-event-filters=document='users/marie'
      • Path pattern; for example, --trigger-event-filters-path-pattern=document='users/*'. For more information, see Understand path patterns.

    You can optionally specify additional configuration, networking, and security options when you deploy a function.

    For a complete reference on the deployment command and its flags, see the gcloud functions deploy documentation.

Example deployments

The following examples demonstrate deployments with the Google Cloud CLI.

Deploy a function for a database in the us-west2 region:

gcloud functions deploy gcfv2-trigger-firestore-node \
--gen2 \
--region=us-west2 \
--trigger-location=us-west2 \
--runtime=nodejs18 \
--source=gs://CLOUD_STORAGE_BUCKET/firestoreEventFunction.zip \
--entry-point=makeUpperCase \
--trigger-event-filters=type=google.cloud.firestore.document.v1.written \
--trigger-event-filters=database='(default)' \
--trigger-event-filters-path-pattern=document='messages/{pushId}'

Deploy a function for a database in the nam5 multi-region:

gcloud functions deploy gcfv2-trigger-firestore-python \
--gen2 \
--region=us-central1 \
--trigger-location=nam5 \
--runtime=python311 \
--source=gs://CLOUD_STORAGE_BUCKET/firestoreEventFunction.zip \
--entry-point=make_upper_case \
--trigger-event-filters=type=google.cloud.firestore.document.v1.written.withAuthContext \
--trigger-event-filters=database='(default)' \
--trigger-event-filters-path-pattern=document='messages/{pushId}'

Limitations

Note the following limitations for Firestore triggers for Cloud Run functions:

  • Cloud Run functions (1st gen) prerequisites an existing "(default)" database in Firestore native mode. It does not support Firestore named databases or Datastore mode. Please use Cloud Run functions (2nd gen) to configure events in such cases.
  • Ordering is not guaranteed. Rapid changes can trigger function invocations in an unexpected order.
  • Events are delivered at least once, but a single event may result in multiple function invocations. Avoid depending on exactly-once mechanics, and write idempotent functions.
  • Firestore in Datastore mode requires Cloud Run functions (2nd gen). Cloud Run functions (1st gen) does not support Datastore mode.
  • A trigger is associated with a single database. You cannot create a trigger that matches multiple databases.
  • Deleting a database does not automatically delete any triggers for that database. The trigger stops delivering events but continues to exist until you delete the trigger.
  • If a matched event exceeds the maximum request size, the event might not be delivered to Cloud Run functions (1st gen).
    • Events not delivered because of request size are logged in platform logs and count towards the log usage for the project.
    • You can find these logs in the Logs Explorer with the message "Event cannot deliver to Cloud function due to size exceeding the limit for 1st gen..." of error severity. You can find the function name under the functionName field. If the receiveTimestamp field is still within an hour from now, you can infer the actual event content by reading the document in question with a snapshot before and after the timestamp.
    • To avoid such cadence, you can:
      • Migrate and upgrade to Cloud Run functions (2nd gen)
      • Downsize the document
      • Delete the Cloud Run functions in question
    • You can turn off the logging itself using exclusions but note that the offending events will still not be delivered.

Eventarc and Firestore locations

Eventarc does not support multi-regions for Firestore event triggers, but you can still create triggers for Firestore databases in multi-region locations. Eventarc maps Firestore multi-region locations to the following Eventarc regions:

Firestore multi-region Eventarc region
nam5 us-central1
eur3 europe-west4

Differences between Cloud Run functions 2nd gen and 1st gen

Cloud Run functions (2nd gen) uses Eventarc events for all runtimes. Previously, Cloud Run functions (1st gen) used Eventarc events for only some runtimes. Eventarc events introduce the following differences from Cloud Run functions (1st gen).

  • The Firestore triggers for Eventarc support additional destinations besides Cloud Run functions. You can route CloudEvents to a number of destinations including, but not limited to Cloud Run, GKE, and Workflows.

  • Firestore triggers for Eventarc retrieve the trigger definition at the start of a database write operation and uses that definition to decide if Firestore should emit an event. The write operation does not take into account any changes to trigger definition that might happen as it runs.

    Cloud Run functions (1st gen) retrieves the trigger definition during the evaluation of the database write, and changes to the trigger during evaluation can affect if Firestore emits an event or not.

For more details, see Cloud Run functions version comparison.