Repurpose your Cloud Functions code as a Firebase Extension

1. Before you begin

A Firebase Extension performs a specific task or set of tasks in response to either HTTP requests or triggering events from other Firebase and Google products like Firebase Cloud Messaging, Cloud Firestore, or Pub/Sub.

What you'll build

In this codelab, you'll build a Firebase extension for geohashing. Once deployed, your extension then converts X- and Y-coordinates into geohashes in response to Firestore events or through callable function invocations. This can be used as an alternative to implementing the geofire library across all your target platforms for storing data, saving you time.

The geohash extension showing in the Firebase console

What you'll learn

  • How to take existing Cloud Functions code and turn it into a distributable Firebase Extension
  • How to set up an extension.yaml file
  • How to store sensitive strings (API keys) in an extension
  • How to allow developers of the extension to configure it to suit their needs
  • How to test and deploy the extension

What you'll need

  • Firebase CLI (install and login)
  • A Google account, like a gmail account
  • Node.js and npm
  • Your favorite development environment

2. Get set up

Get the code

Everything you need for this extension is in a GitHub repo. To get started, grab the code and open it in your favorite development environment.

  1. Unpack the downloaded zip file.
  2. To install the required dependencies, open the terminal in the functions directory and run the npm install command.

Set up Firebase

This codelab highly encourages the use of Firebase emulators. If you want to try out extensions development with a real Firebase project, see create a Firebase project. This codelab uses Cloud Functions, so if you're using a real Firebase project instead of the emulators, you need to upgrade to the Blaze pricing plan.

Want to skip ahead?

You can download a completed version of the codelab. If you get stuck along the way or if you want to see what a completed extension looks like, check out the codelab-end branch of the GitHub repository or download the completed zip.

3. Review the code

  • Open the index.ts file from the zip file. Notice that it contains two Cloud Functions declarations within it.

What do these functions do?

These demo functions are used for geohashing. They take a coordinate pair and turn them into a format that is optimized for geo queries in Firestore. The functions simulate the use of an API call so that you can learn more about handling sensitive data types in extensions. For more information, see the documentation on running Geo queries on data in Firestore.

Function constants

Constants are declared early on, at the top of the index.ts file. Some of these constants are referenced in the extension's defined triggers.

index.ts

import {firestore} from "firebase-functions";
import {initializeApp} from "firebase-admin/app";
import {GeoHashService, ResultStatusCode} from "./fake-geohash-service";
import {onCall} from "firebase-functions/v1/https";
import {fieldValueExists} from "./utils";

const documentPath = "users/{uid}";
const xField = "xv";
const yField = "yv";
const apiKey = "1234567890";
const outputField = "hash";

initializeApp();

const service = new GeoHashService(apiKey);

Firestore Trigger

The first function in the index.ts file looks like this:

index.ts

export const locationUpdate = firestore.document(documentPath)
  .onWrite((change) => {
    // item deleted
    if (change.after == null) {
      return 0;
    }
    // double check that both values exist for computation
    if (
      !fieldValueExists(change.after.data(), xField) ||
      !fieldValueExists(change.after.data(), yField)
    ) {
      return 0;
    }
    const x: number = change.after.data()![xField];
    const y: number = change.after.data()![yField];
    const hash = service.convertToHash(x, y);
    // This is to check whether the hash value has changed. If
    // it hasn't, you don't want to write to the document again as it
    // would create a recursive write loop.
    if (fieldValueExists(change.after.data(), outputField)
      && change.after.data()![outputField] == hash) {
      return 0;
    }
    return change.after.ref
      .update(
        {
          [outputField]: hash.hash,
        }
      );
  });

This function is a Firestore trigger. When a write event occurs in the database, the function reacts to that event by searching for an xv field and a yv field, and, if both of those fields exist, it calculates the geohash and writes the output to a specified document output location. The input document is defined by the users/{uid} constant, which means that the function reads every document written to the users/ collection and then processes a geohash for those documents. It then outputs the hash to a hash field in the same document.

Callable Functions

The next function in the index.ts file looks like this:

index.ts

export const callableHash = onCall((data, context) => {
  if (context.auth == undefined) {
    return {error: "Only authorized users are allowed to call this endpoint"};
  }
  const x = data[xField];
  const y = data[yField];
  if (x == undefined || y == undefined) {
    return {error: "Either x or y parameter was not declared"};
  }
  const result = service.convertToHash(x, y);
  if (result.status != ResultStatusCode.ok) {
    return {error: `Something went wrong ${result.message}`};
  }
  return {result: result.hash};
});

Notice the onCall function. It indicates that this function is a callable function, which can be called from within your client application code. This callable function takes x and y parameters and returns a geohash. Although this function won't be called directly in this codelab, it's included here as an example of something to configure in the Firebase extension.

4. Set up an extension.yaml file

Now that you know what the Cloud Functions code in your extension does, you're ready to package it up for distribution. Every Firebase Extension comes with an extension.yaml file that describes what the extension does and how it behaves.

An extension.yaml file requires some initial metadata about your extension. Each of the following steps helps you understand what all the fields mean and why you need them.

  1. Create an extension.yaml file in the root directory of the project that you downloaded earlier. Start by adding the following:
name: geohash-ext
version: 0.0.1
specVersion: v1beta  # Firebase Extensions specification version (do not edit)

The name of the extension is used as the base of the extension's instance ID (users can install multiple instances of an extension, each with its own ID). Firebase then generates the name of the extension's service accounts and extension-specific resources using that instance ID. The version number indicates the version of your extension. It must follow semantic versioning, and you need to update it whenever you make changes to the functionality of the extension. The extension specification version is used to determine which Firebase extensions specification to follow, in this case, v1beta is used.

  1. Add some user-friendly details to the YAML file:
...

displayName: Latitude and longitude to GeoHash converter
description: A converter for changing your Latitude and longitude coordinates to geohashes.

The display name is a friendly representation of your extension's name when developers interact with your extension. The description gives a brief overview of what the extension does. When the extension is deployed on extensions.dev, it looks something like this:

Geohash Converter extension as seen in on extensions.dev

  1. Specify the license for the code in your extension.
...

license: Apache-2.0  # The license you want for the extension
  1. Indicate who wrote the extension and whether or not billing is required to install it:
...

author:
  authorName: AUTHOR_NAME
  url: https://github.com/Firebase

billingRequired: true

The author section is used to let your users know who to reach out to in the event that they are having issues with the extension or want more information about it. billingRequired is a required parameter and must be set to true since all extensions rely on Cloud Functions, which requires the Blaze plan.

This covers the minimum number of fields required in the extension.yaml file to identify this extension. For more details on other identifying information you can specify in an extension, see the documentation.

5. Convert the Cloud Functions code into an Extensions resource

An extension resource is an item that Firebase creates in the project during an extension's installation. The extension then owns those resources and has a specific service account that operates on them. In this project, those resources are Cloud Functions, which must be defined in the extension.yaml file because the extension will not automatically create resources from code in the functions folder. If your Cloud Functions aren't explicitly declared as a resource, they cannot be deployed when the extension is deployed.

User-defined deployment location

  1. Allow the user to specify the location where they want to deploy this extension and decide whether it would be better to host the extension closer to their end users or closer to their database. In the extension.yaml file, include the option to pick a location.

extension.yaml

You are now ready to write the configuration for the function resource.

  1. In the extension.yaml file, create a resource object for the locationUpdate function. Append the following to the extension.yaml file:
resources:
  - name: locationUpdate
    type: firebaseextensions.v1beta.function
    properties:
      eventTrigger:
        eventType: providers/cloud.firestore/eventTypes/document.write
        resource: projects/${PROJECT_ID}/databases/(default)/documents/users/{uid}

You define the name as the function name defined in the index.ts file of the project. You specify the type of function that is being deployed, which should always be firebaseextensions.v1beta.function, for now. Then, you define the properties of this function. the first property you define is the eventTrigger that is associated with this function. To mirror what the extension currently supports, you use the eventType of providers/cloud.firestore/eventTypes/document.write, which is found in the Write Cloud Functions for your extension documentation. You define the resource as the location of the documents. Since your current goal is to mirror what exists in the code, the document path listens to users/{uid}, with the default database location preceding it.

  1. The extension needs read and write permissions for the Firestore database. At the very end of the extension.yaml file, specify the IAM roles that the extension should have access to in order to work with the database in the developer's Firebase project.
roles:
  - role: datastore.user
    reason: Allows the extension to read / write to your Firestore instance.

The datastore.user role comes from the list of supported IAM roles for extensions. Since the extension is going to be reading and writing, the datastore.user role is a good fit here.

  1. The callable function must be added as well. In the extension.yaml file, create a new resource under the resources property. These properties are specific for a callable function:
  - name: callableHash
    type: firebaseextensions.v1beta.function
    properties:
      httpsTrigger: {}

Although the previous resource used an eventTrigger, here you use an httpsTrigger, which covers both callable functions and HTTPS functions.

Code check

That was a lot of configuration to make your extension.yaml match everything done by the code in your index.ts file. This is what the completed extension.yaml file should look like at this time:

extension.yaml

name: geohash-ext
version: 0.0.1
specVersion: v1beta  # Firebase Extensions specification version (do not edit)

displayName: Latitude and Longitude to GeoHash converter
description: A converter for changing your Latitude and Longitude coordinates to geohashes.

license: Apache-2.0  # The license you want for the extension

author:
  authorName: Sparky
  url: https://github.com/Firebase

billingRequired: true

resources:
  - name: locationUpdate
    type: firebaseextensions.v1beta.function
    properties:
      eventTrigger:
        eventType: providers/cloud.firestore/eventTypes/document.write
        resource: projects/${PROJECT_ID}/databases/(default)/documents/users/{uid}
  - name: callableHash
    type: firebaseextensions.v1beta.function
    properties:
      httpsTrigger: {}

roles:
  - role: datastore.user
    reason: Allows the extension to read / write to your Firestore instance.

Status check

At this point, you have the initial functional pieces of the extension set up, so you can actually try it out using the Firebase emulators!

  1. If you haven't already, call npm run build in the functions folder of the downloaded extensions project.
  2. Create a new directory on your host system and connect that directory to your Firebase project using firebase init.
cd ..
mkdir sample-proj
cd sample-proj
firebase init --project=projectID-or-alias
    This command creates a `firebase.json` file in the directory. In the following steps, you push the configuration specified in this file to Firebase.
  1. From the same directory, run firebase ext:install. Replace /path/to/extension with the absolute path to the directory that contains your extension.yaml file.
firebase ext:install /path/to/extension
    This command does two things:
  • It prompts you to specify the configuration for the extension instance, and it creates an *.env file that contains the configuration information for the instance.
  • It adds the extension instance to the extensions section of your firebase.json. This acts as a map of instance ID to extension version.
  • Since you are deploying the project locally, you can specify that you'd like to use a local file rather than the Google Cloud Secret Manager.

Screenshot of extension installation process showing that Local file is being used for secrets when installing this extension

  1. Start the Firebase emulators with the new configuration:
firebase emulators:start
  1. After running emulators:start, navigate to the Firestore tab in the webview of the emulators.
  2. Add a document into the users collection with an xv number field and yv number field.

A dialog box that is show in the Firebase Emulators to start a collection with the collection ID containing the phrase

  1. If you were successful in installing the extension, the extension creates a new field called hash in the document.

The users collection with a user document having an xv, yv, and hash field.

Clean up to avoid conflicts

  • Once you are done testing, uninstall the extension—you are going to update the extension code and don't want to conflict with the current extension later on.

Extensions allow multiple versions of the same extension to be installed at once, so by uninstalling, you ensure that there are no conflicts with a previously installed extension.

firebase ext:uninstall geohash-ext

The current solution works, but as mentioned in the beginning of the project, there is a hard-coded API key to simulate communicating with a service. How can you use the end-user's API key instead of the one originally supplied? Read on to find out.

6. Make the extension user configurable

At this point in the codelab, you have an extension that is configured for use with the opinionated setup of the functions that you have already written, but what if your user wants to use latitude and longitude instead of y and x for the fields indicating the location on a cartesian plane? Also, how can you get the end user to supply their own API key, rather than let them consume the API key supplied? You could quickly overrun the quota for that API. In this case, you set up and use parameters.

Define basic parameters in the extension.yaml file

Start by converting the items that developers may potentially have a custom configuration for. The first would be the XFIELD and YFIELD parameters.

  1. In the extension.yaml file, add the following code, which uses the XFIELD and the YFIELD field parameters. These parameters live inside the previously defined params YAML property:

extension.yaml

params:
  - param: XFIELD
    label: The X Field Name
    description: >-
      The X Field is also known as the **longitude** value. What does
      your Firestore instance refer to as the X value or the longitude
      value. If no value is specified, the extension searches for
      field 'xv'.
    type: string
    validationRegex: ^\D([0-9a-zA-Z_.]{0,375})$
    validationErrorMessage: >-
      The field can only contain uppercase or lowercase letter, numbers,
      _, and . characters and must be less than 1500 bytes long. The field
      must also not start with a number.
    default: xv
    required: false
    immutable: false
    example: xv
  - param: YFIELD
    label: The Y Field Name
    description: >-
      The Y Field is also known as the **latitude** value. What does
      your Firestore instance refer to as the Y value or the latitude
      value. If no value is specified, the extension searches for
      field 'yv'.
    type: string
    validationRegex: ^\D([0-9a-zA-Z_.]{0,375})$
    validationErrorMessage: >-
      The field can only contain uppercase or lowercase letter, numbers,
      _, and . characters and must be less than 1500 bytes long. The field
      must also not start with a number.
    default: yv
    required: false
    immutable: false
    example: yv
  • param names the parameter in a way that is visible to you, the extension producer. Use this value later on when specifying the parameter values.
  • label is a human-readable identifier to the developer to let them know what the parameter does.
  • description gives a detailed description of the value. Since this supports markdown, it can link to extra documentation, or it can highlight words that might be important to the developer.
  • type defines the input mechanism for how a user would set the parameter value. There are many types that exist, including string, select, multiSelect, selectResource, and secret. To learn more about each of these options, see the documentation.
  • validationRegex constraints the developer entry to a certain regex value (in the example it's based on the simple field name guidelines found here); and if that fails...
  • validationErrorMessage alerts the developer to the failure value.
  • default is what the value would be if the developer did not input any text.
  • required means that the developer is not required to enter any text.
  • immutable allows the developer to update this extension and change this value. In this case, the developer should be able to change field names as their requirements change.
  • example provides an idea of what a valid input may look like.

That was a lot to understand!

  1. You have three more parameters to add to the extension.yaml file before adding a special parameter.
  - param: INPUTPATH
    label: The input document to listen to for changes
    description: >-
      This is the document where you write an x and y value to. Once
      that document has received a value, it notifies the extension to
      calculate a geohash and store that in an output document in a certain
      field. This accepts function [wildcard parameters](https://firebase.google.com/docs/functions/firestore-events#wildcards-parameters)
    type: string
    validationRegex: ^[^/]+(/[^/]*/[^/]*)*/[^/]+$
    validationErrorMessage: >-
      This must point to a document path, not a collection path from the root
      of the database. It must also not start or end with a '/' character.
    required: true
    immutable: false
    example: users/{uid}
  - param: OUTPUTFIELD
    label: Geohash field
    description: >-
      This specifies the field in the output document to store the geohash in.
    type: string
    validationRegex: ^\D([0-9a-zA-Z_.]{0,375})$
    validationErrorMessage: >-
      The field can only contain uppercase or lowercase letter, numbers,
      _, and . characters and must be less than 1500 bytes long. The field
      must also not start with a number.
    required: false
    default: hash
    immutable: false
    example: hash

Define sensitive parameters

Now, you need to manage the API key that the user specifies. This is a sensitive string that should not be stored in plain text in the function. Instead, store this value in the Cloud secret manager. This is a special location in the cloud that stores encrypted secrets, and prevents them from being accidentally leaked. This requires the developer to pay for the use of this service, but it adds an extra layer of security over their API keys and potentially limits fraudulent activity. The user documentation alerts the developer that it is a paid service, so that there aren't any surprises in billing. Overall, the use is similar to the other string resources mentioned above. The only difference is the type which is called secret.

  • In the extension.yaml file, add the following code:

extension.yaml

  - param: APIKEY
    label: GeohashService API Key
    description: >-
      Your geohash service API Key. Since this is a demo, and not a real
      service, you can use : 1234567890.
    type: secret
    required: true
    immutable: false

Update the resource attributes to use parameters

As mentioned previously, the resource (not the function) defines how the resource is observed, so the locationUpdate resource needs to be updated in order to use the new parameter.

  • In the extension.yaml file, add the following code:

extension.yaml

## Change from this
  - name: locationUpdate
    type: firebaseextensions.v1beta.function
    properties:
      eventTrigger:
        eventType: providers/cloud.firestore/eventTypes/document.write
        resource: projects/${PROJECT_ID}/databases/(default)/documents/users/{uid}]

## To this
  - name: locationUpdate
    type: firebaseextensions.v1beta.function
    properties:
      eventTrigger:
        eventType: providers/cloud.firestore/eventTypes/document.write
        resource: projects/${PROJECT_ID}/databases/(default)/documents/${INPUTPATH}

Check the extension.yaml file

  • Review the extension.yaml file. It should look something like this:

extension.yaml

name: geohash-ext
version: 0.0.1
specVersion: v1beta  # Firebase Extensions specification version (do not edit)

displayName: Latitude and Longitude to GeoHash converter
description: A converter for changing your Latitude and Longitude coordinates to geohashes.

license: Apache-2.0  # The license you want to use for the extension

author:
  authorName: Sparky
  url: https://github.com/Firebase

billingRequired: true

params:
  - param: XFIELD
    label: The X Field Name
    description: >-
      The X Field is also known as the **longitude** value. What does
      your Firestore instance refer to as the X value or the longitude
      value. If you don't provide a value for this field, the extension will use 'xv' as the default value.
    type: string
    validationRegex: ^\D([0-9a-zA-Z_.]{0,375})$
    validationErrorMessage: >-
      The field can only contain uppercase or lowercase letter, numbers,
      _, and . characters and must be less than 1500 bytes long. The field
      must also not start with a number.
    default: xv
    required: false
    immutable: false
    example: xv
  - param: YFIELD
    label: The Y Field Name
    description: >-
      The Y Field is also known as the **latitude** value. What does
      your Firestore instance refer to as the Y value or the latitude
      Value. If you don't provide a value for this field, the extension will use 'yv' as the default value.
    type: string
    validationRegex: ^\D([0-9a-zA-Z_.]{0,375})$
    validationErrorMessage: >-
      The field can only contain uppercase or lowercase letter, numbers,
      _, and . characters and must be less than 1500 bytes long. The field
      must also not start with a number.
    default: yv
    required: false
    immutable: false
    example: yv
  - param: INPUTPATH
    label: The input document to listen to for changes
    description: >-
      This is the document where you write an x and y value to. Once
      that document has been modified, it notifies the extension to
      compute a geohash and store that in an output document in a certain
      field. This accepts function [wildcard parameters](https://firebase.google.com/docs/functions/firestore-events#wildcards-parameters)
    type: string
    validationRegex: ^[^/]+(/[^/]*/[^/]*)*/[^/]+$
    validationErrorMessage: >-
      This must point to a document path, not a collection path from the root
      of the database. It must also not start or end with a '/' character.
    required: true
    immutable: false
    example: users/{uid}
  - param: OUTPUTFIELD
    label: Geohash field
    description: >-
      This specifies the field in the output document to store the geohash in.
    type: string
    validationRegex: ^\D([0-9a-zA-Z_.]{0,375})$
    validationErrorMessage: >-
      The field can only contain uppercase or lowercase letter, numbers,
      _, and . characters and must be less than 1500 bytes long. The field
      must also not start with a number.
    required: false
    default: hash
    immutable: false
    example: hash
  - param: APIKEY
    label: GeohashService API Key
    description: >-
      Your geohash service API Key. Since this is a demo, and not a real
      service, you can use : 1234567890.
    type: secret
    required: true
    immutable: false

resources:
  - name: locationUpdate
    type: firebaseextensions.v1beta.function
    properties:
      eventTrigger:
        eventType: providers/cloud.firestore/eventTypes/document.write
        resource: projects/${PROJECT_ID}/databases/(default)/documents/${INPUTPATH}
  - name: callableHash
    type: firebaseextensions.v1beta.function
    properties:
      httpsTrigger: {}

roles:
  - role: datastore.user
    reason: Allows the extension to read / write to your Firestore instance.

Access parameters in code

Now that all of the parameters are configured in the extension.yaml file, add them to the index.ts file.

  • In the index.ts file, replace the default values with process.env.PARAMETER_NAME, which fetches the appropriate parameter values and populates them in the function code deployed on the developer's Firebase project.

index.ts

// Replace this:
const documentPath = "users/{uid}";
const xField = "xv";
const yField = "yv";
const apiKey = "1234567890";
const outputField = "hash";

// with this:
const documentPath = process.env.INPUTPATH!; // this value is ignored since its read from the resource
const xField = process.env.XFIELD!;
const yField = process.env.YFIELD!;
const apiKey = process.env.APIKEY!;
const outputField = process.env.OUTPUTFIELD!;

Normally, you want to perform null checks with the environment variable values, but in this case, you trust that the parameter values are correctly copied. The code is now configured to work with the extension parameters.

7. Create user documentation

Before testing the code on emulators or in the Firebase extensions marketplace, the extension needs to be documented so that developers know what they are getting when they use the extension.

  1. Start by creating the PREINSTALL.md file, which is used to describe the functionality, any prerequisites for installation, and potential billing implications.

PREINSTALL.md

Use this extension to automatically convert documents with a latitude and
longitude to a geohash in your database. Additionally, this extension includes a callable function that allows users to make one-time calls
to convert an x,y coordinate into a geohash.

Geohashing is supported for latitudes between 90 and -90 and longitudes
between 180 and -180.

#### Third Party API Key

This extension uses a fictitious third-party API for calculating the
geohash. You need to supply your own API keys. (Since it's fictitious,
you can use 1234567890 as an API key).

#### Additional setup

Before installing this extension, make sure that you've [set up a Cloud
Firestore database](https://firebase.google.com/docs/firestore/quickstart) in your Firebase project.

After installing this extension, you'll need to:

- Update your client code to point to the callable geohash function if you
want to perform arbitrary geohashes.

Detailed information for these post-installation tasks are provided after
you install this extension.

#### Billing
To install an extension, your project must be on the [Blaze (pay as you
go) plan](https://firebase.google.com/pricing)

- This extension uses other Firebase and Google Cloud Platform services,
which have associated charges if you exceed the service's no-cost tier:
 - Cloud Firestore
 - Cloud Functions (Node.js 16+ runtime. [See
FAQs](https://firebase.google.com/support/faq#extensions-pricing))
 - [Cloud Secret Manager](https://cloud.google.com/secret-manager/pricing)
  1. To save time on writing the README.md for this project, use the convenience method:
firebase ext:info . --markdown > README.md

This combines the contents of your PREINSTALL.md file and additional details about your extension from your extension.yaml file.

Finally, inform the developer of the extension about some additional details regarding the extension that was just installed. The developer may get some additional instructions and information after completing the installation and may get some detailed post-installation tasks like setting up client code here.

  1. Create a POSTINSTALL.md file, and then include the following post installation information:

POSTINSTALL.md

Congratulations on installing the geohash extension!

#### Function information

* **Firestore Trigger** - ${function:locationUpdate.name} was installed
and is invoked when both an x field (${param:XFIELD}) and y field
(${param:YFIELD}) contain a value.

* **Callable Trigger** - ${function:callableHash.name} was installed and
can be invoked by writing the following client code:
 ```javascript
import { getFunctions, httpsCallable } from "firebase/functions";
const functions = getFunctions();
const geoHash = httpsCallable(functions, '${function:callableHash.name}');
geoHash({ ${param:XFIELD}: -122.0840, ${param:YFIELD}: 37.4221 })
  .then((result) => {
    // Read result of the Cloud Function.
    /** @type {any} */
    const data = result.data;
    const error = data.error;
    if (error != null) {
        console.error(`callable error : ${error}`);
    }
    const result = data.result;
    console.log(result);
  });

Monitoring

As a best practice, you can monitor the activity of your installed extension, including checks on its health, usage, and logs.

The output rendering looks something like this when it's deployed:

<img src="img/82b54a5c6ca34b3c.png" alt="A preview of the latitude and longitude geohash converter extension in the firebase console"  width="957.00" />


## Test the extension with the full configuration
Duration: 03:00


It's time to make sure that the user-configurable extension is working the way it is intended.

* Change into the functions folder and ensure that the latest compiled version of the extensions exists. In the extensions project functions directory, call:

```console
npm run build

This recompiles the functions so the latest source code is ready for deployment alongside the extension when it's deployed to an emulator or to Firebase directly.

Next, create a new directory to test the extension from. Since the extension was developed from existing functions, do not test from the folder that the extension was configured in as that also attempts to deploy the functions and Firebase rules alongside it.

Install and test with the Firebase emulators

  1. Create a new directory on your host system and connect that directory to your Firebase project using firebase init.
mkdir sample-proj
cd sample-proj
firebase init --project=projectID-or-alias
  1. From that directory, run firebase ext:install to install the extension. Replace /path/to/extension with the absolute path to the directory that contains your extension.yaml file. This starts the installation process for your extension and creates a .env file that contains your configurations before pushing the configuration to Firebase or to the emulators.
firebase ext:install /path/to/extension
  • Since you are deploying the project locally, specify that you would like to use a local file rather than the Google Cloud Secret Manager.

da928c65ffa8ce15.png

  1. Start the local emulator suite:
firebase emulators:start

Install and test with a real Firebase project

You can install your extension in an actual Firebase project. It is recommended to use a test project for your testing. Use this testing workflow if you want to test your extension's end-to-end flow or if your extension's trigger isn't yet supported by the Firebase emulator suite (see the Extensions emulator option). The emulators currently support HTTP request-triggered functions and background event-triggered functions for Cloud Firestore, Realtime Database, and Pub/Sub.

  1. Create a new directory on your host system and connect that directory to your Firebase project using firebase init.
cd ..
mkdir sample-proj
cd sample-proj
firebase init --project=projectID-or-alias
  1. Then, from that directory, run firebase ext:install to install the extension. Replace /path/to/extension with the absolute path to the directory that contains your extension.yaml file. This starts the installation process for your extension and creates a .env file that contains your configurations before pushing the configuration to Firebase or to the emulators.
firebase ext:install /path/to/extension
  • Since you want to deploy to Firebase directly, and want to use the Google Cloud Secret Manager, you need to activate the Secret Manager API before installing the extension.
  1. Deploy to your Firebase project.
firebase deploy

Test out the extension

  1. After running firebase deploy or firebase emulators:start, navigate to the Firestore tab of either the Firebase console or the webview of the emulators, as appropriate.
  2. Add a document into the collection specified by the x field and y field. In this case, updated documents are located at u/{uid} with an x field of xv and a y field of yv.

Firebase Emulators Screen to add a Firestore Record

  1. If you were successful in installing the extension, the extension creates a new field called hash in the document after you save the two fields.

Firestore database screen from an emulator showing hash added

8. Congratulations!

You've successfully converted your first Cloud Function into a Firebase Extension!

You added an extension.yaml file and configured it so developers can select how they would like your extension to be deployed. You then created user documentation that provides guidance for what the developers of the extension should do prior to setting up the extension and what steps they might need to take after having successfully installed the extension.

You now know the key steps required to convert a Firebase Function into a distributable Firebase Extension.

What's next?