Get started building an extension Stay organized with collections Save and categorize content based on your preferences.
This page walks you through the steps required to build a simple FirebaseExtension, which you can install in your projects or share with others. Thissimple example of a Firebase Extension will watch your Realtime Database formessages 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 buildenvironment with the required tools.
Install Node.js 16 or newer. One way to install Node is by usingnvm (ornvm-windows).
Install or update to the latest version of theFirebase CLI. Toinstall or update using
npm, run this command:npminstall-gfirebase-tools
Now use the Firebase CLI to initialize a new extension project:
Create a directory for your extension and
cdinto it:mkdirrtdb-uppercase-messages &&cdrtdb-uppercase-messagesRun the Firebase CLI's
ext:dev:initcommand:firebaseext:dev:initWhen prompted, choose JavaScript as the language for functions (but notethat you can also use TypeScript when you develop your own extension), and,when asked to install dependencies, answer "yes". (Accept the defaults forany other options.) This command will set up a skeleton codebase for anew extension, from which you can start developing your extension.
Usefirebase ext:dev:init to initialize a new extension directory.
2. Try the example extension using the emulator
When the Firebase CLI initialized the new extensions directory, it created asimple example function and anintegration-tests directory that contains thefiles necessary to run an extension using the Firebase emulator suite.
Try running the example extension in the emulator:
Change to the
integration-testsdirectory:cdfunctions/integration-testsStart the emulator with a demo project:
firebaseemulators:start--project=demo-testThe emulator loads the extension into a predefined "dummy" project(
demo-test). The extension so far consists of a single HTTP-triggeredfunction,greetTheWorld, which returns a "hello world" message whenaccessed.With the emulator still running, try the extension's
greetTheWorldfunction by visiting the URL it printed when you started it.Your browser displays the message "Hello World from greet-the-world".
The source code for this function is in the extension's
functionsdirectory. Open the source in the editor or IDE of your choice:functions/index.js
constfunctions=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)constconsumerProvidedGreeting=process.env.GREETING;// And here we reference an auto-populated parameter// (its value is provided by Firebase after installation)constinstanceId=process.env.EXT_INSTANCE_ID;constgreeting=`${consumerProvidedGreeting} World from${instanceId}`;res.send(greeting);});While the emulator is running, it will automatically reload any changes youmake to your Functions code. Try making a small change to the
greetTheWorldfunction:functions/index.js
constgreeting=`${consumerProvidedGreeting} everyone, from${instanceId}`;Save your changes. The emulator will reload your code, and now, when youvisit the function URL, you'll see the updated greeting.
Using the extensions emulator can speed up development by letting you quicklytest and iterate on your code.
More information
Learn more aboutusing the extensions emulator.
3. Add basic information to extension.yaml
Now that you have a development environment set up and are running theextensions emulator, you can start writing your own extension.
As a modest first step, edit the predefined extension metadata to reflect theextension you want to write instead ofgreet-the-world. This metadata isstored in theextension.yaml file.
Open
extension.yamlin your editor, and replace the entire contents of thefile with the following:name:rtdb-uppercase-messagesversion:0.0.1specVersion: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 caseauthor:authorName:Your Nameurl:https://your-site.example.comlicense:Apache-2.0# Required license# Public URL for the source code of your extensionsourceUrl:https://github.com/your-name/your-repoNote the naming convention used in the
namefield: official Firebaseextensions are named with a prefix indicating the primary Firebase productthe extension operates on, followed by a description of what the extensiondoes. You should use the same convention in your own extensions.Since you've changed the name of your extension, you should also update youremulator configuration with the new name:
- In
functions/integration-tests/firebase.json, changegreet-the-worldtortdb-uppercase-messages. - Rename
functions/integration-tests/extensions/greet-the-world.envtofunctions/integration-tests/extensions/rtdb-uppercase-messages.env.
- In
There are still some remnants of thegreet-the-world extension remaining inyour extension code, but leave them for now. You'll update those in the next fewsections.
The
extension.yamlfile contains metadata about your extension. The mostbasic of this metadata is your extension's name and a description of what itdoes.Name your extensions with the following format:
<firebase-product>-<description-of-tasks-performed>.
More information
Theextension.yaml reference hasa full specification of the file; however, this documentation will discussspecific uses of this file as you need to use them.
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 CloudFunction that performs the core task of your extension, which isto watch your Realtime Database for messages and convert them to upper case.
Open the source for the extension's functions (in the extension's
functionsdirectory) in the editor or IDE of your choice. Replace itscontents with the following:functions/index.js
import{database,logger}from"firebase-functions/v1";constapp=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'exportconstmakeuppercase=database.ref("/messages/{pushId}/uppercase").onCreate(async(snapshot,context)=>{// Grab the current value of what was written to the Realtime Database.constoriginal=snapshot.val();// Convert it to upper case.logger.log("Uppercasing",context.params.pushId,original);constuppercase=original.toUpperCase();// Setting an "uppercase" sibling in the Realtime Database.constupperRef=snapshot.ref.parent.child("upper");awaitupperRef.set(uppercase);});The old function, which you replaced, was an HTTP-triggered function, whichran when an HTTP endpoint was accessed. The new function is triggered byreal-time database events: it watches for new items at a particular pathand, when one is detected, it writes the uppercase version of the value backto the database.
By the way, this new file uses ECMAScript module syntax (
importandexport) instead of CommonJS (require). To use ES modules in Node,specify"type": "module"infunctions/package.json:{"name":"rtdb-uppercase-messages","main":"index.js","type":"module",…}Every function in your extension must be declared in the
extension.yamlfile. The example extension declaredgreetTheWorldas the extension's onlyCloud Function; now that you've replaced it withmakeuppercase, you alsoneed to update its declaration.Open
extension.yamland add aresourcesfield:resources:-name:makeuppercasetype:firebaseextensions.v1beta.functionproperties: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}/originalruntime:"nodejs18"Since your extension is now using Realtime Database as a trigger, you needto update your emulator configuration to run the RTDB emulator alongside theCloud Functions emulator:
If the emulator is still running, stop it by pressing Ctrl-C.
From the
functions/integration-testsdirectory, run the followingcommand:firebaseinitemulatorsWhen asked, skip setting up a default project, then select the Functionsand Database emulators. Accept the default ports and allow the setuptool to download any required files.
Restart the emulator:
firebaseemulators:start--project=demo-test
Try out your updated extension:
Open the Database emulator UI using the link the emulator printedwhen you started it.
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
makeuppercasefunction should trigger and add a childrecord to message 11 with the contents"upper": "RECIPE". Take a lookat the logs and the database tabs of the emulator UI to confirmthe expected results.- Field:
Try adding some more children to the
messagesnode({"original":"any text"}). Whenever you add a new record, theextension should add anuppercasefield containing the uppercasecontents of theoriginalfield.
You now have a complete, though simple, extension that operates on an RTDBinstance. In the sections that follow, you will refine this extension with someadditional features. Then, you'll get the extension ready to distribute toothers, and finally, learn how to publish your extension on Extensions Hub.
- The functions that make up your extension's logic must be both defined asCloud Functions code and declared as an extension resource in the
extension.yamlfile. - You can write functions that trigger when HTTP endpoints are accessed, or inresponse to events emitted by Firebase products, Google Cloud products, andother extensions.
More information
- Learn more aboutwriting Cloud Functions for extensions,including more about the supported event triggers.
- The
extension.yamlreferencehas a full specification of the file; however, this documentation will discussspecific uses of this file as you need to use them. - TheCloud Functions for Firebase documentation has generalinformation 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 theproject and its data using a per-instance service account. Each account has theminimum set of permissions needed to operate. For this reason, you mustexplicitly declare any IAM roles your extension requires; when users installyour extension, Firebase creates a service account with these roles granted anduses it to run the extension.
You don't need to declare roles to trigger off a product's events, but you doneed to declare a role to otherwise interact with it. Because the function youadded in the last step writes to Realtime Database, you need to add thefollowing declaration toextension.yaml:
roles:-role:firebasedatabase.adminreason:Allows the extension to write to RTDB.Similarly, you declare the Google APIs that an extension uses in theapisfield. When users install your extension, they will be asked if they wantto automatically enable these APIs for their project. This is typically onlynecessary for non-Firebase Google APIs, and isn't needed for this guide.
- Declare any IAM role your extension needs in the
rolesfield ofextensions.yaml. When installed, extensions are automatically granted theseroles. - Declare any Google APIs your extension needs in the
apisfield ofextensions.yaml. When users install your extension, they can elect toautomatically enable these APIs for their project. - For documentation purposes, declare any non-Google APIs your extension needsin the
externalServicesfield ofextensions.yaml.
More information
- Learn more aboutsetting up appropriate access for an extension.
- The
extension.yamlreferencehas a full specification of the file; however, this documentation will discussspecific 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 locationfor incoming messages. Sometimes, watching a specific location really is whatyou want, such as when your extension operates on a database structure that youuse exclusively for your extension. However, most of the time, you will want tomake these values configurable by users who install your extension in theirprojects. This way, users can make use of your extension to work with theirexisting database setup.
Make the path that the extension watches for new messages user-configurable:
In the
extension.yamlfile, add aparamssection:-param:MESSAGE_PATHlabel:Message pathdescription:>-What is the path at which the original text of a message can be found?type:stringdefault:/messages/{pushId}/originalrequired:trueimmutable:falseThis defines a new string parameter that users will be prompted to set whenthey install your extension.
Still in the
extension.yamlfile, go back to yourmakeuppercasedeclaration and change theresourcefield to the following:resource:projects/_/instances/${DATABASE_INSTANCE}/refs/${param:MESSAGE_PATH}The
${param:MESSAGE_PATH}token is a reference to the parameter you justdefined. When your extension runs, this token will be replaced by whatevervalue the user configured for that parameter, with the result that themakeuppercasefunction will listen to the path the user specified. You canuse this syntax to reference any user-defined parameter anywhere inextension.yaml(and inPOSTINSTALL.md—more on that later).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 towatch for changes. Change the trigger definition to reference theuser-defined value instead:
functions/index.js
exportconstmakeuppercase=database.ref(process.env.MESSAGE_PATH).onCreateNote that in Firebase Extensions, this change is purely for the sake ofdocumentation: when a Cloud Function is deployed as part of an extension, ituses the trigger definition from the
extension.yamlfile and ignores thevalue specified in the function definition. Nevertheless, it's a good ideato document in your code where this value comes from.You might find it disappointing to make a code change that has no runtimeeffect, but the important lesson to take away is that you can access anyuser-defined parameter in your function code and use it as an ordinary valuein the function's logic. As a nod to this capability, add the following logstatement to demonstrate that you are indeed accessing the value that theuser defined:
functions/index.js
exportconstmakeuppercase=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....Normally, users are prompted to provide values for parameters when theyinstall an extension. When you use the emulator for testing and development,however, you skip the installation process, so you instead provide valuesfor user-defined parameters using an
envfile.Open
functions/integration-tests/extensions/rtdb-uppercase-messages.envand replace theGREETINGdefinition with the following:MESSAGE_PATH=/msgs/{pushId}/originalNotice that the path above is different from the default path and from thepath you defined previously; this is just to prove to yourself when you tryyour updated extension that your definition is taking effect.
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
makeuppercasefunction should trigger as it did before, but now it should also print theuser-defined parameter to the console log.- Field:
- You can give users the ability to customize your extension for their needs bydeclaring user-defined parameters in the
extension.yamlfile. Users areprompted to define these values when they install your extension. - You can reference user-defined parameter values within the
extension.yamlfile and in thePOSTINSTALL.mdfile using the following syntax:${param:PARAMETER_NAME} - You can access user-defined parameter values within your Cloud Functions codeas environment variables:
process.env.PARAMETER_NAME - When testing using the emulator, define user parameters in the
<extension-name>.envfile.
More information
Learn more aboutsetting up and using parameters in your extension.
7. Provide event hooks for user-defined logic
You've already seen, as an extension author, how a Firebase product can triggeryour extension-provided logic: the creation of new records in Realtime Databasetriggers yourmakeuppercase function. Your extension can have an analogousrelationship with the users who install your extension: yourextension cantrigger logic that theuser defines.
An extension can providesynchronous hooks,asynchronous hooks, or both.Synchronous hooks give users a way to perform tasks that block the completion ofone of the extension's functions. This can be useful, for example, to give usersa way to perform custom preprocessing before an extension does its work.
In this guide, you'll add an asynchronous hook to your extension, which willenable users to define their own processing steps to be run after your extensionwrites the uppercase message to Realtime Database. Asynchronous hooks useEventarc to triggeruser-defined functions. Extensions declare the types of events they emit, andwhen users install the extension, they choose which event types they'reinterested in. If they choose at least one event, Firebase will provision anEventarc channel for the extension as part of the installation process. Userscan then deploy their own cloud functions that listen on that channel andtrigger when the extension publishes new events.
Follow these steps to add an asynchronous hook:
In the
extension.yamlfile, add the following section, which declares theone event type the extension emits:events:-type:test-publisher.rtdb-uppercase-messages.v1.completedescription:>-Occurs when message uppercasing completes. The event subject will containthe RTDB URL of the uppercase message.Event types must be universally unique; to ensure uniqueness, always nameyour events using the following format:
<publisher-id>.<extension-id>.<version>.<description>. (You don't have apublisher ID yet, so just usetest-publisherfor now.)At the end of the
makeuppercasefunction, add some code that publishes anevent of the type you just declared:functions/index.js
// Import the Eventarc library:import{initializeApp}from"firebase-admin/app";import{getEventarc}from"firebase-admin/eventarc";constapp=initializeApp();// In makeuppercase, after upperRef.set(uppercase), add:// Set eventChannel to a newly-initialized channel, or `undefined` if events// aren't enabled.consteventChannel=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_CHANNELenvironment variable is defined only when the user enabled at least oneevent type. ifEVENTARC_CHANNELis not defined, the code does not attemptto publish any events.You can attach extra information to an Eventarc event. In the example above,the event has a
subjectfield that contains a reference to thenewly-created value, and adatapayload that contains the original anduppercase messages. User-defined functions that trigger off the event canmake use of this information.Normally, the
EVENTARC_CHANNELandEXT_SELECTED_EVENTSenvironmentvariables are defined based on the options the user selected duringinstallation. For testing with the emulator, manually define these variablesin thertdb-uppercase-messages.envfile:EVENTARC_CHANNEL=locations/us-central1/channels/firebaseEXT_SELECTED_EVENTS=test-publisher.rtdb-uppercase-messages.v1.complete
At this point, you have completed the steps needed to add an asynchronous eventhook to your extension.
To try out this new feature that you have just implemented, in the next fewsteps, assume the role of a user who is installing the extension:
From the
functions/integration-testsdirectory, initialize a new Firebaseproject:firebaseinitfunctionsWhen prompted, decline to set up a default project, select JavaScript as theCloud Functions language, and install the required dependencies. Thisproject represents auser's project, which has your extension installed.
Edit
integration-tests/functions/index.jsand paste the following code: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";constapp=initializeApp();exportconstextraemphasis=onCustomEventPublished("test-publisher.rtdb-uppercase-messages.v1.complete",async(event)=>{logger.info("Received makeuppercase completed event",event);constrefUrl=event.subject;constref=getDatabase().refFromURL(refUrl);constupper=(awaitref.get()).val();returnref.set(`${upper}!!!`);});This is an example of a post-processing function a user might write. In thiscase, the function listens for the extension to publish a
completeevent,and when triggered, adds three exclamation points to the newly-uppercasedmessage.Restart the emulator. The emulator will load the extension's functions aswell as the post-processing function the "user" defined.
Visit the database emulator UI and edit the root node of the database, usingthe path you defined above:
- Field:
msgs - Type:
json - Value:
{"11": {"original": "recipe"}}
When you save your database changes, the extension's
makeuppercasefunction and the user'sextraemphasisfunction should trigger in sequence,resulting in theupperfield getting the valueRECIPE!!!.- Field:
- Your extensions can include hooks that let users insert their own logic intoyour extension's basic operation.
- User hooks can be synchronous, which block the execution of an extension untilthey complete. Extensions often use synchronous hooks to perform user-definedpreprocessing tasks.
- User hooks can also be asynchronous, as in the example above. Asynchronoushooks can be used to run user-defined logic that is not critical for the extension to function correctly.
More information
Learn more aboutadding hooks for user-defined logic,including both asynchronous and synchronous hooks.
8. Add lifecycle event handlers
The extension you've written so far processes messages as they are created. Butwhat if your users already have a database of messages when they install theextension? Firebase Extensions has a feature calledlifecycle event hooks thatyou can use to trigger actions when your extension gets installed, updated, orreconfigured. In this section, you will use lifecycle event hooks to backfill aproject's existing message database with uppercased messages when a userinstalls your extension.
Firebase Extensions uses Cloud Tasks to run your lifecycle event handlers. Youdefine event handlers using Cloud Functions; whenever an instance of yourextension reaches one of the supported lifecycle events, if you have defined ahandler, it will add the handler to a Cloud Tasks queue. Cloud Tasks will thenasynchronously execute the handler. While a lifecycle event handler is running,the Firebase console will report to the user that the extension instance has aprocessing task in progress. It's up to your handler function to report ongoingstatus and task completion back to the user.
To add a lifecycle event handler that backfills existing messages, do thefollowing:
Define a new Cloud Function that's triggered by task queue events:
functions/index.js
import{tasks}from"firebase-functions/v1";import{getDatabase}from"firebase-admin/database";import{getExtensions}from"firebase-admin/extensions";import{getFunctions}from"firebase-admin/functions";exportconstbackfilldata=tasks.taskQueue().onDispatch(async()=>{constbatch=awaitgetDatabase().ref(process.env.MESSAGE_PATH).parent.parent.orderByChild("upper").limitToFirst(20).get();constpromises=[];for(constkeyinbatch.val()){constmsg=batch.child(key);if(msg.hasChild("original") &&!msg.hasChild("upper")){constupper=msg.child("original").val().toUpperCase();promises.push(msg.child("upper").ref.set(upper));}}awaitPromise.all(promises);if(promises.length >0){constqueue=getFunctions().taskQueue("backfilldata",process.env.EXT_INSTANCE_ID);returnqueue.enqueue({});}else{returngetExtensions().runtime().setProcessingState("PROCESSING_COMPLETE","Backfill complete.");}});Notice that the function only processes a few records before adding itselfback to the task queue. This is a commonly used strategy to deal withprocessing tasks that cannot complete within the timeout window of a CloudFunction. Since you can't predict how many messages a user might alreadyhave in their database when they install your extension, this strategy is agood fit.
In the
extension.yamlfile, declare your backfill function as an extensionresource that has thetaskQueueTriggerproperty:resources:-name:makeuppercase...-name:backfilldatatype:firebaseextensions.v1beta.functiondescription:>-Backfill existing messages with uppercase versionsproperties:runtime:"nodejs18"taskQueueTrigger:{}Then declare the function as the handler for the
onInstalllifecycleevent:lifecycleEvents:onInstall:function:backfilldataprocessingMessage:Uppercasing existing messagesAlthough backfilling existing messages is nice to have, the extension couldstill function without it. In situations like this, you should make runningthe lifecycle event handlers optional.
To do so, add a new parameter to
extension.yaml:-param:DO_BACKFILLlabel:Backfill existing messagesdescription:>-Generate uppercase versions of existing messages?type:selectrequired:trueoptions:-label:Yesvalue:true-label:Novalue:falseThen at the beginning of the backfill function, check the value of the
DO_BACKFILLparameter and exit early if it's not set:functions/index.js
if(!process.env.DO_BACKFILL){returngetExtensions().runtime().setProcessingState("PROCESSING_COMPLETE","Backfill skipped.");}
With the above changes, the extension will now convert existing messages touppercase when it is installed.
Up to this point, you used the extension emulator to develop your extension andtest ongoing changes. However, the extension emulator skips the installationprocess, so to test youronInstall event handler, you'll need to install theextension in a real project. That's just as well though, since with the additionof this automatic backfill feature, the tutorial extension is now code-complete!
Lifecycle events are triggered when users perform certain extension managementtasks:
- 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 lifecycleevent handler back to the user. Users will see an extension's currentprocessing 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 thisproblem by splitting your task over several function invocations.
If your extension includes lifecycle event handlers that are not critical forthe extension to function, you should make the execution of the handler userconfigurable.
More information
Learn more abouthandling your extension's lifecycle events.
9. Deploy into a real Firebase project
Although the extensions emulator is a great tool for rapidly iterating on anextension during development, at some point you'll want to try it in a realproject.
To do so, first set up a new project with some services enabled:
- In theFirebase console, add a newproject.
- Upgrade your projectto the pay-as-you-go Blaze plan. Cloud Functions for Firebase requires yourproject to have a billing account, so you also need a billing account toinstall an extension.
- In your new project,enable Real-time Database.
- Since you want to test your extension's ability to backfill existing data oninstallation, import some sample data into your real-time database instance:
- Download someseed RTDB data.
- On the Real-time Database page of the Firebase console, click (more) > Import JSONand select the file you just downloaded.
To enable the backfill function to use the
orderByChildmethod, configurethe database to index messages on the value ofupper:{"rules":{".read":false,".write":false,"messages":{".indexOn":"upper"}}}
Now install your extension from local source into the new project:
Create a new directory for your Firebase project:
mkdir ~/extensions-live-test && cd ~/extensions-live-testInitialize a Firebase project in the working directory:
firebaseinitdatabaseWhen prompted, select the project you just created.
Install the extension into your local Firebase project:
firebaseext:install/path/to/rtdb-uppercase-messagesHere you can see what the user experience is like when installing anextension using the Firebase CLI tool. Be sure to select "yes" when theconfiguration tool asks if you want to backfill your existing database.
After you select configuration options, the Firebase CLI will save yourconfiguration in the
extensionsdirectory and record the extension sourcelocation in thefirebase.jsonfile. Collectively, these two records arecalled theextensions manifest. Users can use the manifest to save theirextensions configuration and deploy it to different projects.Deploy your extension configuration to your live project:
firebasedeploy--onlyextensions
If all goes well, the Firebase CLI should upload your extension to your projectand install it. After installation completes, the backfill task will run and, ina few minutes, your database will be updated with uppercase messages. Add somenew nodes to the messages database and make sure the extension is also workingfor new messages.
- Users can create an extension manifest using the
firebase ext:installcommand. You can also use this command to install an extension from localsource. - Deploy an extension configuration from a manifest into a live project using
firebase deploy. - Although not demonstrated here, users can also install extensions into theirprojects from Extensions Hub.
More information
See the user documentation onmanaging project configurations with the Extensions manifest.
10. Write documentation
Before you share your extension with users, make sure you're providing enoughdocumentation for them to be successful.
When you initialized the extension project, the Firebase CLI created stubversions of the minimum required documentation. Update these files to accuratelyreflect the extension you've built.
extension.yaml
You've already been updating this file as you've developed this extension, soyou don't need to make any more updates right now.
However, don't overlook the importance of the documentation contained in thisfile. In addition to an extension's crucial identifying information—name,description, author, official repository location—theextension.yamlfile contains user-facing documentation for every resource and user-configurableparameter. 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 yourextension: briefly describe what the extension does, explain any prerequisites,and give the user information on the billing implications of installing theextension. If you have a website with additional information, this is also agood place to link it.
The text of this file is displayed to the user in Extensions Hub and by thefirebase ext:info command.
Here is an example of a PREINSTALL file:
Use this extension to automatically convert strings to upper case when added toa 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 recordwith upper-cased text: MESSAGE_ID: { "original": MESSAGE_TEXT, "upper": UPPERCASE_MESSAGE_TEXT, }#### Additional setupBefore installing this extension, make sure that you've[set up Realtime Database](https://firebase.google.com/docs/database/quickstart)in your Firebase project.#### BillingTo 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: - Realtime Database - Cloud Functions (Node.js 10+ runtime) [See FAQs](https://firebase.google.com/support/faq#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 successfullyinstalled your extension: for example, follow-up setup steps, an example of theextension in action, and so on.
The contents of POSTINSTALL.md are displayed in the Firebase console after anextension is configured and installed. You can reference user parameters in thisfile and they will be replaced by the configured values.
Here is an example post-install file for the tutorial extension:
### See it in actionYou 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 extensionWe recommend adding data by pushing -- for example,`firebase.database().ref().push()` -- because pushing assigns an automaticallygenerated ID to the node in the database. During retrieval, these nodes areguaranteed to be ordered by the time they were added. Learn more about readingand writing data for your platform (iOS, Android, or Web) in the[Realtime Database documentation](https://firebase.google.com/docs/database/).### MonitoringAs a best practice, you can[monitor the activity](https://firebase.google.com/docs/extensions/manage-installed-extensions#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 extensionin theCHANGELOG.md file.
Since the example extension has never been published before, the change log hasonly one entry:
## Version 0.0.1Initial release of the _Convert messages to upper case_ extension.README.md
Most extensions also provide a readme file for the benefit of users visiting theextension's repository. you can write this file by hand or generate a read meusing 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 shouldprovide users. Many extensions require more detailed documentation for users tosuccessfully use them. When this is the case, you should write additionaldocumentation and host it somewhere you can point users to.
For the purpose of this guide, skip writing more extensive documentation.
- At a minimum, every extension should provide user documentation in thefollowing files:
extension.yaml,PREINSTALL.md,POSTINSTALL.md, andCHANGELOG.md. - You should also provide users with more detailed documentation when necessary.
More information
See thedocumentation on writing documentation.
11. Publish on Extensions Hub
Now that your extension is code complete and documented, you are ready to shareit with the world on Extensions Hub. But since this is just a tutorial, don'tactually do that. Go and start writing your own extension using what you'velearned 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 doit:
- If you are publishing your first extension,register as an extension publisher. Whenyou register as an extensions publisher, you create a publisher ID that letsusers quickly identify you as the author of your extensions.
Host your extension's source code in a publicly verifiable location. Whenyour code is available from a verifiable source, Firebase can publish yourextension directly from this location. Doing so helps ensure that you'republishing the currently released version of your extension, and helps usersby letting them examine the code they're installing into their projects.
Currently, this means making your extension available in a public GitHubrepository.
Upload your extension to Extensions Hub using the
firebase ext:dev:uploadcommand.Go to your publisher dashboard in the Firebase console, find the extensionyou just uploaded, and click "Publish to Extensions Hub". This requests areview from our review staff, which can take a few days. If approved, theextension will be published to Extensions Hub. If rejected, you'll get amessage explaining the reason; you can then address the reported issues andresubmit for review.
- To share extensions on Extensions Hub, you must be registered as a publisher.
- Publishing from a verifiable source is required, and it gives users assurancethat the code they are installing is the same code that they can examine onGitHub.
- Use the
firebase ext:dev:uploadcommand to upload an extension to ExtensionsHub. - Submit your extensions for review from the publisher dashboard.
More information
Learn more aboutregistering as a publisher andpublishing an extension.
Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.
Last updated 2026-02-04 UTC.