This page walks you through the steps required to build a simple Firebase
Extension, which you can install in your projects or share with others. This
simple example of a Firebase Extension will watch your Realtime Database for
messages and convert them to upper case.

## 1. Set up your environment and initialize a project

Before you can start building an extension, you'll need to set up a build
environment with the required tools.

1. Install Node.js 16 or newer. One way to install Node is by using
   [nvm](https://github.com/creationix/nvm)
   (or [nvm-windows](https://github.com/coreybutler/nvm-windows)).

2. Install or update to the latest version of the [Firebase CLI](https://firebase.google.com/docs/cli.md.txt). To
   install or update using `npm`, run this command:

   ```bash
   npm install -g firebase-tools
   ```

Now use the Firebase CLI to initialize a new extension project:

1. Create a directory for your extension and `cd` into it:

   ```bash
   mkdir rtdb-uppercase-messages && cd rtdb-uppercase-messages
   ```
2. Run the Firebase CLI's `ext:dev:init` command:

   ```bash
   firebase ext:dev:init
   ```

   When prompted, choose JavaScript as the language for functions (but note
   that you can also use TypeScript when you develop your own extension), and,
   when asked to install dependencies, answer "yes". (Accept the defaults for
   any other options.) This command will set up a skeleton codebase for a
   new extension, from which you can start developing your extension.

<br />

> [!IMPORTANT]
> **Key idea**
>
> <br />
>
> Use `firebase ext:dev:init` to initialize a new extension directory.

<br />

## 2. Try the example extension using the emulator

When the Firebase CLI initialized the new extensions directory, it created a
simple example function and an `integration-tests` directory that contains the
files necessary to run an extension using the Firebase emulator suite.

Try running the example extension in the emulator:

1. Change to the `integration-tests` directory:

   ```bash
   cd functions/integration-tests
   ```
2. Start the emulator with a demo project:

   ```bash
   firebase emulators:start --project=demo-test
   ```

   The emulator loads the extension into a predefined "dummy" project
   (`demo-test`). The extension so far consists of a single HTTP-triggered
   function, `greetTheWorld`, which returns a "hello world" message when
   accessed.
3. With the emulator still running, try the extension's `greetTheWorld`
   function by visiting the URL it printed when you started it.

   Your browser displays the message "Hello World from greet-the-world".
4. The source code for this function is in the extension's `functions`
   directory. Open the source in the editor or IDE of your choice:

   *functions/index.js*

   ```javascript
   const functions = require("firebase-functions/v1");

   exports.greetTheWorld = functions.https.onRequest((req, res) => {
     // Here we reference a user-provided parameter
     // (its value is provided by the user during installation)
     const consumerProvidedGreeting = process.env.GREETING;

     // And here we reference an auto-populated parameter
     // (its value is provided by Firebase after installation)
     const instanceId = process.env.EXT_INSTANCE_ID;

     const greeting = `${consumerProvidedGreeting} World from ${instanceId}`;

     res.send(greeting);
   });
   ```
5. While the emulator is running, it will automatically reload any changes you
   make to your Functions code. Try making a small change to the
   `greetTheWorld` function:

   *functions/index.js*

   ```javascript
   const greeting = `${consumerProvidedGreeting} everyone, from ${instanceId}`;
   ```

   Save your changes. The emulator will reload your code, and now, when you
   visit the function URL, you'll see the updated greeting.

<br />

> [!IMPORTANT]
> **Key idea**
>
> <br />
>
> Using the extensions emulator can speed up development by letting you quickly
> test and iterate on your code.
>
> **More information**
>
> Learn more about
> [using the extensions emulator](https://firebase.google.com/docs/emulator-suite/use_extensions.md.txt).

<br />

## 3. Add basic information to extension.yaml

Now that you have a development environment set up and are running the
extensions emulator, you can start writing your own extension.

As a modest first step, edit the predefined extension metadata to reflect the
extension you want to write instead of `greet-the-world`. This metadata is
stored in the `extension.yaml` file.

1. Open `extension.yaml` in your editor, and replace the entire contents of the
   file with the following:

   ```yaml
   name: rtdb-uppercase-messages
   version: 0.0.1
   specVersion: v1beta  # Firebase Extensions specification version; don't change

   # Friendly display name for your extension (~3-5 words)
   displayName: Convert messages to upper case

   # Brief description of the task your extension performs (~1 sentence)
   description: >-
     Converts messages in RTDB to upper case

   author:
     authorName: Your Name
     url: https://your-site.example.com

   license: Apache-2.0  # Required license

   # Public URL for the source code of your extension
   sourceUrl: https://github.com/your-name/your-repo
   ```

   Note the naming convention used in the `name` field: official Firebase
   extensions are named with a prefix indicating the primary Firebase product
   the extension operates on, followed by a description of what the extension
   does. You should use the same convention in your own extensions.
2. Since you've changed the name of your extension, you should also update your
   emulator configuration with the new name:

   1. In `functions/integration-tests/firebase.json`, change `greet-the-world` to `rtdb-uppercase-messages`.
   2. Rename `functions/integration-tests/extensions/greet-the-world.env` to `functions/integration-tests/extensions/rtdb-uppercase-messages.env`.

There are still some remnants of the `greet-the-world` extension remaining in
your extension code, but leave them for now. You'll update those in the next few
sections.

<br />

> [!IMPORTANT]
> **Key ideas**
>
> <br />
>
> - The `extension.yaml` file contains metadata about your extension. The most
>   basic of this metadata is your extension's name and a description of what it
>   does.
>
> - Name your extensions with the following format:
>   `<firebase-product>-<description-of-tasks-performed>`.
>
> **More information**
>
> The [`extension.yaml` reference](https://firebase.google.com/docs/extensions/reference/extension-yaml.md.txt) has
> a full specification of the file; however, this documentation will discuss
> specific uses of this file as you need to use them.

<br />

## 4. Write a Cloud Function and declare it as an extension resource

Now you can get started writing some code. In this step, you will write a Cloud
Function that performs the core task of your extension, which is
to watch your Realtime Database for messages and convert them to upper case.

1. Open the source for the extension's functions (in the extension's
   `functions` directory) in the editor or IDE of your choice. Replace its
   contents with the following:

   *functions/index.js*

   ```javascript
   import { database, logger } from "firebase-functions/v1";

   const app = initializeApp();

   // Listens for new messages added to /messages/{pushId}/original and creates an
   // uppercase version of the message to /messages/{pushId}/uppercase
   // for all databases in 'us-central1'
   export const makeuppercase = database
     .ref("/messages/{pushId}/uppercase")
     .onCreate(async (snapshot, context) => {
       // Grab the current value of what was written to the Realtime Database.
       const original = snapshot.val();

       // Convert it to upper case.
       logger.log("Uppercasing", context.params.pushId, original);
       const uppercase = original.toUpperCase();

       // Setting an "uppercase" sibling in the Realtime Database.
       const upperRef = snapshot.ref.parent.child("upper");
       await upperRef.set(uppercase);
   });
   ```

   The old function, which you replaced, was an HTTP-triggered function, which
   ran when an HTTP endpoint was accessed. The new function is triggered by
   real-time database events: it watches for new items at a particular path
   and, when one is detected, it writes the uppercase version of the value back
   to the database.

   By the way, this new file uses ECMAScript module syntax (`import` and
   `export`) instead of CommonJS (`require`). To use ES modules in Node,
   specify `"type": "module"` in `functions/package.json`:

   ```json
   {
     "name": "rtdb-uppercase-messages",
     "main": "index.js",
     "type": "module",
     ...
   }
   ```
2. Every function in your extension must be declared in the `extension.yaml`
   file. The example extension declared `greetTheWorld` as the extension's only
   Cloud Function; now that you've replaced it with `makeuppercase`, you also
   need to update its declaration.

   Open `extension.yaml` and add a `resources` field:

   ```yaml
   resources:
     - name: makeuppercase
       type: firebaseextensions.v1beta.function
       properties:
         eventTrigger:
           eventType: providers/google.firebase.database/eventTypes/ref.create
           # DATABASE_INSTANCE (project's default instance) is an auto-populated
           # parameter value. You can also specify an instance.
           resource: projects/_/instances/${DATABASE_INSTANCE}/refs/messages/{pushId}/original
         runtime: "nodejs18"
   ```
3. Since your extension is now using Realtime Database as a trigger, you need
   to update your emulator configuration to run the RTDB emulator alongside the
   Cloud Functions emulator:

   1. If the emulator is still running, stop it by pressing Ctrl-C.

   2. From the `functions/integration-tests` directory, run the following
      command:

      ```bash
      firebase init emulators
      ```

      When asked, skip setting up a default project, then select the Functions
      and Database emulators. Accept the default ports and allow the setup
      tool to download any required files.
   3. Restart the emulator:

      ```bash
      firebase emulators:start --project=demo-test
      ```
4. Try out your updated extension:

   1. Open the Database emulator UI using the link the emulator printed
      when you started it.

   2. Edit the root node of the database:

      - **Field:** `messages`
      - **Type:** `json`
      - **Value:** `{"11": {"original": "recipe"}}`

      If everything is set up correctly, when you save your database changes,
      the extension's `makeuppercase` function should trigger and add a child
      record to message 11 with the contents `"upper": "RECIPE"`. Take a look
      at the logs and the database tabs of the emulator UI to confirm
      the expected results.
   3. Try adding some more children to the `messages` node
      (`{"original":"any text"}`). Whenever you add a new record, the
      extension should add an `uppercase` field containing the uppercase
      contents of the `original` field.

You now have a complete, though simple, extension that operates on an RTDB
instance. In the sections that follow, you will refine this extension with some
additional features. Then, you'll get the extension ready to distribute to
others, and finally, learn how to publish your extension on Extensions Hub.

<br />

> [!IMPORTANT]
> **Key ideas**
>
> <br />
>
> - The functions that make up your extension's logic must be both defined as Cloud Functions code and declared as an extension resource in the `extension.yaml` file.
> - You can write functions that trigger when HTTP endpoints are accessed, or in response to events emitted by Firebase products, Google Cloud products, and other extensions.
>
> **More information**
>
> - Learn more about [writing Cloud Functions for extensions](https://firebase.google.com/docs/extensions/publishers/functions.md.txt), including more about the supported event triggers.
> - The [`extension.yaml` reference](https://firebase.google.com/docs/extensions/reference/extension-yaml.md.txt) has a full specification of the file; however, this documentation will discuss specific uses of this file as you need to use them.
> - The [Cloud Functions for Firebase documentation](https://firebase.google.com/docs/functions.md.txt) has general information about using Cloud Functions, not specific to Firebase Extensions.

## 5. Declare APIs and roles

Firebase grants each instance of an installed extension limited access to the
project and its data using a per-instance service account. Each account has the
minimum set of permissions needed to operate. For this reason, you must
explicitly declare any IAM roles your extension requires; when users install
your extension, Firebase creates a service account with these roles granted and
uses it to run the extension.

You don't need to declare roles to trigger off a product's events, but you do
need to declare a role to otherwise interact with it. Because the function you
added in the last step writes to Realtime Database, you need to add the
following declaration to `extension.yaml`:

```yaml
roles:
  - role: firebasedatabase.admin
    reason: Allows the extension to write to RTDB.
```

Similarly, you declare the Google APIs that an extension uses in the `apis`
field. When users install your extension, they will be asked if they want
to automatically enable these APIs for their project. This is typically only
necessary for non-Firebase Google APIs, and isn't needed for this guide.

<br />

> [!IMPORTANT]
> **Key ideas**
>
> <br />
>
> - Declare any IAM role your extension needs in the `roles` field of `extensions.yaml`. When installed, extensions are automatically granted these roles.
> - Declare any Google APIs your extension needs in the `apis` field of `extensions.yaml`. When users install your extension, they can elect to automatically enable these APIs for their project.
> - For documentation purposes, declare any non-Google APIs your extension needs in the `externalServices` field of `extensions.yaml`.
>
> **More information**
>
> - Learn more about [setting up appropriate access for an extension](https://firebase.google.com/docs/extensions/publishers/access.md.txt).
> - The [`extension.yaml` reference](https://firebase.google.com/docs/extensions/reference/extension-yaml.md.txt) has a full specification of the file; however, this documentation will discuss specific uses of this file as you need to use them.

## 6. Define user-configurable parameters

The function you created in the last two steps watched a specific RTDB location
for incoming messages. Sometimes, watching a specific location really is what
you want, such as when your extension operates on a database structure that you
use exclusively for your extension. However, most of the time, you will want to
make these values configurable by users who install your extension in their
projects. This way, users can make use of your extension to work with their
existing database setup.

Make the path that the extension watches for new messages user-configurable:

1. In the `extension.yaml` file, add a `params` section:

   ```yaml
   - param: MESSAGE_PATH
     label: Message path
     description: >-
       What is the path at which the original text of a message can be found?
     type: string
     default: /messages/{pushId}/original
     required: true
     immutable: false
   ```

   This defines a new string parameter that users will be prompted to set when
   they install your extension.
2. Still in the `extension.yaml` file, go back to your `makeuppercase`
   declaration and change the `resource` field to the following:

   ```yaml
   resource: projects/_/instances/${DATABASE_INSTANCE}/refs/${param:MESSAGE_PATH}
   ```

   The `${param:MESSAGE_PATH}` token is a reference to the parameter you just
   defined. When your extension runs, this token will be replaced by whatever
   value the user configured for that parameter, with the result that the
   `makeuppercase` function will listen to the path the user specified. You can
   use this syntax to reference any user-defined parameter anywhere in
   `extension.yaml` (and in `POSTINSTALL.md`---more on that later).
3. You can also access user-defined parameters from your functions code.

   In the function you wrote in the last section, you hard-coded the path to
   watch for changes. Change the trigger definition to reference the
   user-defined value instead:

   *functions/index.js*

   ```javascript
   export const makeuppercase = database.ref(process.env.MESSAGE_PATH).onCreate
   ```

   Note that in Firebase Extensions, this change is purely for the sake of
   documentation: when a Cloud Function is deployed as part of an extension, it
   uses the trigger definition from the `extension.yaml` file and ignores the
   value specified in the function definition. Nevertheless, it's a good idea
   to document in your code where this value comes from.
4. You might find it disappointing to make a code change that has no runtime
   effect, but the important lesson to take away is that you can access any
   user-defined parameter in your function code and use it as an ordinary value
   in the function's logic. As a nod to this capability, add the following log
   statement to demonstrate that you are indeed accessing the value that the
   user defined:

   *functions/index.js*

   ```javascript
   export const makeuppercase = database.ref(process.env.MESSAGE_PATH).onCreate(
     async (snapshot, context) => {
       logger.log("Found new message at ", snapshot.ref);

       // Grab the current value of what was written to the Realtime Database.
       ...
   ```
5. Normally, users are prompted to provide values for parameters when they
   install an extension. When you use the emulator for testing and development,
   however, you skip the installation process, so you instead provide values
   for user-defined parameters using an `env` file.

   Open `functions/integration-tests/extensions/rtdb-uppercase-messages.env`
   and replace the `GREETING` definition with the following:

   ```bash
   MESSAGE_PATH=/msgs/{pushId}/original
   ```

   Notice that the path above is different from the default path and from the
   path you defined previously; this is just to prove to yourself when you try
   your updated extension that your definition is taking effect.
6. Now, restart the emulator and once again visit the database emulator UI.

   Edit the root node of the database, using the path you defined above:
   - **Field:** `msgs`
   - **Type:** `json`
   - **Value:** `{"11": {"original": "recipe"}}`

   When you save your database changes, the extension's `makeuppercase`
   function should trigger as it did before, but now it should also print the
   user-defined parameter to the console log.

<br />

> [!IMPORTANT]
> **Key ideas**
>
> <br />
>
> - You can give users the ability to customize your extension for their needs by declaring user-defined parameters in the `extension.yaml` file. Users are prompted to define these values when they install your extension.
> - You can reference user-defined parameter values within the `extension.yaml` file and in the `POSTINSTALL.md` file using the following syntax: `${param:PARAMETER_NAME}`
> - You can access user-defined parameter values within your Cloud Functions code as environment variables: `process.env.PARAMETER_NAME`
> - When testing using the emulator, define user parameters in the `<extension-name>.env` file.
>
> **More information**
>
> Learn more about [setting up and using parameters in your extension](https://firebase.google.com/docs/extensions/publishers/parameters.md.txt).

<br />

## 7. Provide event hooks for user-defined logic

You've already seen, as an extension author, how a Firebase product can trigger
your extension-provided logic: the creation of new records in Realtime Database
triggers your `makeuppercase` function. Your extension can have an analogous
relationship with the users who install your extension: your *extension* can
trigger logic that the *user* defines.

An extension can provide *synchronous hooks* , *asynchronous hooks*, or both.
Synchronous hooks give users a way to perform tasks that block the completion of
one of the extension's functions. This can be useful, for example, to give users
a way to perform custom preprocessing before an extension does its work.

In this guide, you'll add an asynchronous hook to your extension, which will
enable users to define their own processing steps to be run after your extension
writes the uppercase message to Realtime Database. Asynchronous hooks use
[Eventarc](https://cloud.google.com/eventarc/docs/targets) to trigger
user-defined functions. Extensions declare the types of events they emit, and
when users install the extension, they choose which event types they're
interested in. If they choose at least one event, Firebase will provision an
Eventarc channel for the extension as part of the installation process. Users
can then deploy their own cloud functions that listen on that channel and
trigger when the extension publishes new events.

Follow these steps to add an asynchronous hook:

1. In the `extension.yaml` file, add the following section, which declares the
   one event type the extension emits:

   ```yaml
   events:
     - type: test-publisher.rtdb-uppercase-messages.v1.complete
       description: >-
         Occurs when message uppercasing completes. The event subject will contain
         the RTDB URL of the uppercase message.
   ```

   Event types must be universally unique; to ensure uniqueness, always name
   your events using the following format:
   `<publisher-id>.<extension-id>.<version>.<description>`. (You don't have a
   publisher ID yet, so just use `test-publisher` for now.)
2. At the end of the `makeuppercase` function, add some code that publishes an
   event of the type you just declared:

   *functions/index.js*

   ```javascript
   // Import the Eventarc library:
   import { initializeApp } from "firebase-admin/app";
   import { getEventarc } from "firebase-admin/eventarc";

   const app = initializeApp();

   // In makeuppercase, after upperRef.set(uppercase), add:

   // Set eventChannel to a newly-initialized channel, or `undefined` if events
   // aren't enabled.
   const eventChannel =
     process.env.EVENTARC_CHANNEL &&
     getEventarc().channel(process.env.EVENTARC_CHANNEL, {
       allowedEventTypes: process.env.EXT_SELECTED_EVENTS,
     });

   // If events are enabled, publish a `complete` event to the configured
   // channel.
   eventChannel &&
     eventChannel.publish({
       type: "test-publisher.rtdb-uppercase-messages.v1.complete",
       subject: upperRef.toString(),
       data: {
         "original": original,
         "uppercase": uppercase,
       },
     });
   ```

   This example code takes advantage of the fact that the `EVENTARC_CHANNEL`
   environment variable is defined only when the user enabled at least one
   event type. if `EVENTARC_CHANNEL` is not defined, the code does not attempt
   to publish any events.

   You can attach extra information to an Eventarc event. In the example above,
   the event has a `subject` field that contains a reference to the
   newly-created value, and a `data` payload that contains the original and
   uppercase messages. User-defined functions that trigger off the event can
   make use of this information.
3. Normally, the `EVENTARC_CHANNEL` and `EXT_SELECTED_EVENTS` environment
   variables are defined based on the options the user selected during
   installation. For testing with the emulator, manually define these variables
   in the `rtdb-uppercase-messages.env` file:

   ```bash
   EVENTARC_CHANNEL=locations/us-central1/channels/firebase
   EXT_SELECTED_EVENTS=test-publisher.rtdb-uppercase-messages.v1.complete
   ```

At this point, you have completed the steps needed to add an asynchronous event
hook to your extension.

To try out this new feature that you have just implemented, in the next few
steps, assume the role of a user who is installing the extension:

1. From the `functions/integration-tests` directory, initialize a new Firebase
   project:

   ```bash
   firebase init functions
   ```

   When prompted, decline to set up a default project, select JavaScript as the
   Cloud Functions language, and install the required dependencies. This
   project represents a *user's* project, which has your extension installed.
2. Edit `integration-tests/functions/index.js` and paste the following code:

   ```javascript
   import { logger } from "firebase-functions/v1";
   import { onCustomEventPublished } from "firebase-functions/v2/eventarc";

   import { initializeApp } from "firebase-admin/app";
   import { getDatabase } from "firebase-admin/database";

   const app = initializeApp();

   export const extraemphasis = onCustomEventPublished(
     "test-publisher.rtdb-uppercase-messages.v1.complete",
     async (event) => {
       logger.info("Received makeuppercase completed event", event);

       const refUrl = event.subject;
       const ref = getDatabase().refFromURL(refUrl);
       const upper = (await ref.get()).val();
       return ref.set(`${upper}!!!`);
     }
   );
   ```

   This is an example of a post-processing function a user might write. In this
   case, the function listens for the extension to publish a `complete` event,
   and when triggered, adds three exclamation points to the newly-uppercased
   message.
3. Restart the emulator. The emulator will load the extension's functions as
   well as the post-processing function the "user" defined.

4. Visit the database emulator UI and edit the root node of the database, using
   the path you defined above:

   - **Field:** `msgs`
   - **Type:** `json`
   - **Value:** `{"11": {"original": "recipe"}}`

   When you save your database changes, the extension's `makeuppercase`
   function and the user's `extraemphasis` function should trigger in sequence,
   resulting in the `upper` field getting the value `RECIPE!!!`.

<br />

> [!IMPORTANT]
> **Key ideas**
>
> <br />
>
> - Your extensions can include hooks that let users insert their own logic into your extension's basic operation.
> - User hooks can be synchronous, which block the execution of an extension until they complete. Extensions often use synchronous hooks to perform user-defined preprocessing tasks.
> - User hooks can also be asynchronous, as in the example above. Asynchronous hooks can be used to run user-defined logic that is not critical for the extension to function correctly.
>
> **More information**
>
> Learn more about
> [adding hooks for user-defined logic](https://firebase.google.com/docs/extensions/publishers/user-hooks.md.txt),
> including both asynchronous and synchronous hooks.

<br />

## 8. Add lifecycle event handlers

The extension you've written so far processes messages as they are created. But
what if your users already have a database of messages when they install the
extension? Firebase Extensions has a feature called *lifecycle event hooks* that
you can use to trigger actions when your extension gets installed, updated, or
reconfigured. In this section, you will use lifecycle event hooks to backfill a
project's existing message database with uppercased messages when a user
installs your extension.

Firebase Extensions uses Cloud Tasks to run your lifecycle event handlers. You
define event handlers using Cloud Functions; whenever an instance of your
extension reaches one of the supported lifecycle events, if you have defined a
handler, it will add the handler to a Cloud Tasks queue. Cloud Tasks will then
asynchronously execute the handler. While a lifecycle event handler is running,
the Firebase console will report to the user that the extension instance has a
processing task in progress. It's up to your handler function to report ongoing
status and task completion back to the user.

To add a lifecycle event handler that backfills existing messages, do the
following:

1. Define a new Cloud Function that's triggered by task queue events:

   *functions/index.js*

   ```javascript
   import { tasks } from "firebase-functions/v1";

   import { getDatabase } from "firebase-admin/database";
   import { getExtensions } from "firebase-admin/extensions";
   import { getFunctions } from "firebase-admin/functions";

   export const backfilldata = tasks.taskQueue().onDispatch(async () => {
     const batch = await getDatabase()
       .ref(process.env.MESSAGE_PATH)
       .parent.parent.orderByChild("upper")
       .limitToFirst(20)
       .get();

     const promises = [];
     for (const key in batch.val()) {
       const msg = batch.child(key);
       if (msg.hasChild("original") && !msg.hasChild("upper")) {
         const upper = msg.child("original").val().toUpperCase();
         promises.push(msg.child("upper").ref.set(upper));
       }
     }
     await Promise.all(promises);

     if (promises.length > 0) {
       const queue = getFunctions().taskQueue(
         "backfilldata",
         process.env.EXT_INSTANCE_ID
       );
       return queue.enqueue({});
     } else {
       return getExtensions()
         .runtime()
         .setProcessingState("PROCESSING_COMPLETE", "Backfill complete.");
     }
   });
   ```

   Notice that the function only processes a few records before adding itself
   back to the task queue. This is a commonly used strategy to deal with
   processing tasks that cannot complete within the timeout window of a Cloud
   Function. Since you can't predict how many messages a user might already
   have in their database when they install your extension, this strategy is a
   good fit.
2. In the `extension.yaml` file, declare your backfill function as an extension
   resource that has the `taskQueueTrigger` property:

   ```yaml
   resources:
     - name: makeuppercase
       ...
     - name: backfilldata
       type: firebaseextensions.v1beta.function
       description: >-
         Backfill existing messages with uppercase versions
       properties:
         runtime: "nodejs18"
         taskQueueTrigger: {}
   ```

   Then declare the function as the handler for the `onInstall` lifecycle
   event:

   ```yaml
   lifecycleEvents:
     onInstall:
       function: backfilldata
       processingMessage: Uppercasing existing messages
   ```
3. Although backfilling existing messages is nice to have, the extension could
   still function without it. In situations like this, you should make running
   the lifecycle event handlers optional.

   To do so, add a new parameter to `extension.yaml`:

   ```yaml
   - param: DO_BACKFILL
     label: Backfill existing messages
     description: >-
       Generate uppercase versions of existing messages?
     type: select
     required: true
     options:
       - label: Yes
         value: true
       - label: No
         value: false
   ```

   Then at the beginning of the backfill function, check the value of the
   `DO_BACKFILL` parameter and exit early if it's not set:

   *functions/index.js*

   ```javascript
   if (!process.env.DO_BACKFILL) {
     return getExtensions()
       .runtime()
       .setProcessingState("PROCESSING_COMPLETE", "Backfill skipped.");
   }
   ```

With the above changes, the extension will now convert existing messages to
uppercase when it is installed.

Up to this point, you used the extension emulator to develop your extension and
test ongoing changes. However, the extension emulator skips the installation
process, so to test your `onInstall` event handler, you'll need to install the
extension in a real project. That's just as well though, since with the addition
of this automatic backfill feature, the tutorial extension is now code-complete!

<br />

> [!IMPORTANT]
> **Key ideas**
>
> <br />
>
> - Lifecycle events are triggered when users perform certain extension management
>   tasks:
>
>   - Installing an instance of an extension
>   - Updating an instance of an extension to a new version
>   - Reconfiguring an instance of an extension
> - You can define functions that trigger on your extension's lifecycle events.
>
> - Use the Admin SDK's extension runtime API to report the status of a lifecycle
>   event handler back to the user. Users will see an extension's current
>   processing status in the Firebase console.
>
> - Functions that operate over your entire database (such as backfill operations)
>   often cannot complete before the Cloud Function times out. You can avoid this
>   problem by splitting your task over several function invocations.
>
> - If your extension includes lifecycle event handlers that are not critical for
>   the extension to function, you should make the execution of the handler user
>   configurable.
>
> **More information**
>
> Learn more about [handling your extension's lifecycle events](https://firebase.google.com/docs/extensions/publishers/lifecycle-events.md.txt).

<br />

## 9. Deploy into a real Firebase project

Although the extensions emulator is a great tool for rapidly iterating on an
extension during development, at some point you'll want to try it in a real
project.

To do so, first set up a new project with some services enabled:

1. In the [Firebase console](https://console.firebase.google.com/), add a new project.
2. [Upgrade your project](https://firebase.google.com/docs/projects/billing/firebase-pricing-plans.md.txt#upgrade-spark-to-blaze) to the pay-as-you-go Blaze plan. Cloud Functions for Firebase requires your project to have a billing account, so you also need a billing account to install an extension.
3. In your new project, [enable Real-time Database](https://console.firebase.google.com/project/_/database).
4. Since you want to test your extension's ability to backfill existing data on installation, import some sample data into your real-time database instance:
   1. Download some [seed RTDB data](https://raw.githubusercontent.com/firebase/extensions/next/samples/rtdb-uppercase-messages/rtdb-seed-data.json).
   2. On the Real-time Database page of the Firebase console, click **(more) \> Import JSON** and select the file you just downloaded.
5. To enable the backfill function to use the `orderByChild` method, configure
   the database to index messages on the value of `upper`:

   ```yaml
   {
     "rules": {
       ".read": false,
       ".write": false,
       "messages": {
         ".indexOn": "upper"
       }
     }
   }
   ```

Now install your extension from local source into the new project:

1. Create a new directory for your Firebase project:

       mkdir ~/extensions-live-test && cd ~/extensions-live-test

2. Initialize a Firebase project in the working directory:

   ```bash
   firebase init database
   ```

   When prompted, select the project you just created.
3. Install the extension into your local Firebase project:

   ```bash
   firebase ext:install /path/to/rtdb-uppercase-messages
   ```

   Here you can see what the user experience is like when installing an
   extension using the Firebase CLI tool. Be sure to select "yes" when the
   configuration tool asks if you want to backfill your existing database.

   After you select configuration options, the Firebase CLI will save your
   configuration in the `extensions` directory and record the extension source
   location in the `firebase.json` file. Collectively, these two records are
   called the *extensions manifest*. Users can use the manifest to save their
   extensions configuration and deploy it to different projects.
4. Deploy your extension configuration to your live project:

   ```bash
   firebase deploy --only extensions
   ```

If all goes well, the Firebase CLI should upload your extension to your project
and install it. After installation completes, the backfill task will run and, in
a few minutes, your database will be updated with uppercase messages. Add some
new nodes to the messages database and make sure the extension is also working
for new messages.

<br />

> [!IMPORTANT]
> **Key ideas**
>
> <br />
>
> - Users can create an extension manifest using the `firebase ext:install` command. You can also use this command to install an extension from local source.
> - Deploy an extension configuration from a manifest into a live project using `firebase deploy`.
> - Although not demonstrated here, users can also install extensions into their projects from Extensions Hub.
>
> **More information**
>
> See the user documentation on [managing project configurations with the Extensions manifest](https://firebase.google.com/docs/extensions/manifest.md.txt).

<br />

## 10. Write documentation

Before you share your extension with users, make sure you're providing enough
documentation for them to be successful.

When you initialized the extension project, the Firebase CLI created stub
versions of the minimum required documentation. Update these files to accurately
reflect the extension you've built.

### extension.yaml

You've already been updating this file as you've developed this extension, so
you don't need to make any more updates right now.

However, don't overlook the importance of the documentation contained in this
file. In addition to an extension's crucial identifying information---name,
description, author, official repository location---the `extension.yaml`
file contains user-facing documentation for every resource and user-configurable
parameter. This information is surfaced to users in the Firebase console,
Extensions Hub, and Firebase CLI.

### PREINSTALL.md

In this file, provide information the user needs before they install your
extension: briefly describe what the extension does, explain any prerequisites,
and give the user information on the billing implications of installing the
extension. If you have a website with additional information, this is also a
good place to link it.

The text of this file is displayed to the user in Extensions Hub and by the
`firebase ext:info` command.

Here is an example of a PREINSTALL file:

    Use this extension to automatically convert strings to upper case when added to
    a specified Realtime Database path.

    This extension expects a database layout like the following example:

        "messages": {
          MESSAGE_ID: {
            "original": MESSAGE_TEXT
          },
          MESSAGE_ID: {
            "original": MESSAGE_TEXT
          },
        }

    When you create new string records, this extension creates a new sibling record
    with upper-cased text:

        MESSAGE_ID: {
          "original": MESSAGE_TEXT,
          "upper": UPPERCASE_MESSAGE_TEXT,
        }

    #### Additional setup

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

    #### Billing

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

    - This extension uses other Firebase and Google Cloud Platform services, which
      have associated charges if you exceed the service's no-cost tier:
      - Realtime Database
      - Cloud Functions (Node.js 10+ runtime)
        [See FAQs](/docs/functions/faq-and-troubleshooting#extensions-pricing)
    - If you enable events,
      [Eventarc fees apply](https://cloud.google.com/eventarc/pricing).

### POSTINSTALL.md

This file contains information useful for users after they have successfully
installed your extension: for example, follow-up setup steps, an example of the
extension in action, and so on.

The contents of POSTINSTALL.md are displayed in the Firebase console after an
extension is configured and installed. You can reference user parameters in this
file and they will be replaced by the configured values.

Here is an example post-install file for the tutorial extension:

    ### See it in action

    You can test out this extension right away!

    1.  Go to your
        [Realtime Database dashboard](https://console.firebase.google.com/project/${param:PROJECT_ID}/database/${param:PROJECT_ID}/data) in the Firebase console.

    1.  Add a message string to a path that matches the pattern `${param:MESSAGE_PATH}`.

    1.  In a few seconds, you'll see a sibling node named `upper` that contains the
        message in upper case.

    ### Using the extension

    We recommend adding data by pushing -- for example,
    `firebase.database().ref().push()` -- because pushing assigns an automatically
    generated ID to the node in the database. During retrieval, these nodes are
    guaranteed to be ordered by the time they were added. Learn more about reading
    and writing data for your platform (iOS, Android, or Web) in the
    [Realtime Database documentation](https://firebase.google.com/docs/database.md.txt).

    ### Monitoring

    As a best practice, you can
    [monitor the activity](https://firebase.google.com/docs/extensions/manage-installed-extensions.md.txt#monitor)
    of your installed extension, including checks on its health, usage, and logs.

### CHANGELOG.md

You should also document the changes you make between releases of an extension
in the `CHANGELOG.md` file.

Since the example extension has never been published before, the change log has
only one entry:

    ## Version 0.0.1

    Initial release of the _Convert messages to upper case_ extension.

### README.md

Most extensions also provide a readme file for the benefit of users visiting the
extension's repository. you can write this file by hand or generate a read me
using the command.

For the purpose of this guide, skip writing a readme file.

### Additional documentation

The documentation discussed above is the minimum set of documentation you should
provide users. Many extensions require more detailed documentation for users to
successfully use them. When this is the case, you should write additional
documentation and host it somewhere you can point users to.

For the purpose of this guide, skip writing more extensive documentation.

<br />

> [!IMPORTANT]
> **Key ideas**
>
> <br />
>
> - At a minimum, every extension should provide user documentation in the following files: `extension.yaml`, `PREINSTALL.md`, `POSTINSTALL.md`, and `CHANGELOG.md`.
> - You should also provide users with more detailed documentation when necessary.
>
> **More information**
>
> See the [documentation on writing documentation](https://firebase.google.com/docs/extensions/publishers/user-documentation.md.txt).

<br />

## 11. Publish on Extensions Hub

Now that your extension is code complete and documented, you are ready to share
it with the world on Extensions Hub. But since this is just a tutorial, don't
actually do that. Go and start writing your own extension using what you've
learned here and in the rest of the Firebase Extensions publisher documentation,
and by examining the source of the official, Firebase-written, extensions.

When you're ready to publish your work on Extensions Hub here's how you will do
it:

1. If you are publishing your first extension, [register as an extension publisher](https://firebase.google.com/docs/extensions/publishers/register.md.txt). When you register as an extensions publisher, you create a publisher ID that lets users quickly identify you as the author of your extensions.
2. Host your extension's source code in a publicly verifiable location. When
   your code is available from a verifiable source, Firebase can publish your
   extension directly from this location. Doing so helps ensure that you're
   publishing the currently released version of your extension, and helps users
   by letting them examine the code they're installing into their projects.

   Currently, this means making your extension available in a public GitHub
   repository.
3. Upload your extension to Extensions Hub using the `firebase ext:dev:upload`
   command.

4. Go to your publisher dashboard in the Firebase console, find the extension
   you just uploaded, and click "Publish to Extensions Hub". This requests a
   review from our review staff, which can take a few days. If approved, the
   extension will be published to Extensions Hub. If rejected, you'll get a
   message explaining the reason; you can then address the reported issues and
   resubmit for review.

<br />

> [!IMPORTANT]
> **Key ideas**
>
> <br />
>
> - To share extensions on Extensions Hub, you must be registered as a publisher.
> - Publishing from a verifiable source is required, and it gives users assurance that the code they are installing is the same code that they can examine on GitHub.
> - Use the `firebase ext:dev:upload` command to upload an extension to Extensions Hub.
> - Submit your extensions for review from the publisher dashboard.
>
> **More information**
>
> Learn more about [registering as a publisher](https://firebase.google.com/docs/extensions/publishers/register.md.txt) and [publishing an extension](https://firebase.google.com/docs/extensions/publishers/upload-and-publish.md.txt).

<br />