Firebase for HubSpot Apps: Build the Backend

HubSpot Developers| 00:18:16|May 12, 2026
Chapters10
The video explains building a Firebase-backed backend to support backend tasks like YouTube integration and token syncing, including scaffolding, multi-tenant data layout, and deploying a health endpoint across dev and prod environments.

HubSpot Developers shows how to bootstrap a Firebase-backed backend for HubSpot apps, from enabling Firebase on two Google Cloud projects to deploying Firestore rules and a health endpoint, with emulators and multi-tenant path design.

Summary

HubSpot Developers walks through building a backend for HubSpot UI extensions using Firebase. The host explains why a backend is needed beyond sandboxed UI apps, then guides viewers through enabling Firebase on two Google Cloud projects (demo CCD dev and demo CC prod), collecting project IDs, and configuring aliases. The video covers initializing Firestore with a global deny-all rule, setting up a multi-tenant path layout (app ID then portal ID), and creating two shared utilities to be used by every endpoint. Viewers learn to configure TypeScript support, install dependencies, and prepare the environment for emulators (Firestore and Functions), including enabling the UI for emulators and testing a health endpoint. The host demonstrates deploying Firestore rules, creating an npmrc to handle a private registry, and wiring up CORS and error handling middleware for Cloud Functions. Finally, the video walks through local emulator testing (curling a health endpoint), alias management for dev/prod environments, and the basics of deploying both Firestore and functions to both environments with environment-specific files (env.dev and env.prod templates). Throughout, the emphasis is on a robust, maintainable backend structure that scales across multiple HubSpot portals from a single creator console application.

Key Takeaways

  • Firestore is locked down by default with a global deny-all rule to prevent accidental data exposure, and access is only opened via cloud functions using the admin SDK.
  • A multi-tenant layout is implemented: every document path is keyed by app ID first, then portal ID second, to support one app serving many HubSpot portals.
  • Two shared backend utilities are created early (the fire store path helper and a CORS/error-handling middleware) so endpoints stay consistent and less error-prone.
  • Emulators are configured (Firestore and Functions) and tested locally, with a dedicated health endpoint that confirms the stack is alive via a curl request.
  • Alias management via Firebase RC lets you deterministically switch between dev and prod environments, ensuring deployments target the correct project.
  • Environment-specific configuration is prepared with env.dev and env.prod templates, enabling runtime selection of project secrets without changing code.
  • An npmrc file is added to ensure dependency installation works smoothly in restricted registry environments, illustrating practical developer workflow challenges.

Who Is This For?

Essential viewing for HubSpot developers who need a robust backend for UI extensions, and for teams migrating backend logic to Firebase and Cloud Functions. It’s especially helpful for those setting up multi-tenant data layouts and dev/prod separation across HubSpot portals.

Notable Quotes

"By the end of this video, we'll enable Firebase on both our dev and prod Google Cloud projects and initialize dev backend."
Sets the agenda and scope for enabling Firebase on both environments.
"A global deny all means every collection is blocked unless you explicitly open it through a cloud function using the admin SDK."
Highlights a critical security stance for Firestore data.
"One app, many portals, each portal with its own tokens."
Explains the multi-tenant path layout essential to the backend design.
"The smallest endpoint we can ship—the /health route—will deploy already gets to use them."
Demonstrates how to validate the emulator stack with a basic endpoint.
"Firebase autoloads the env.dev or prod based on the active alias."
Describes how runtime configuration is selected for dev vs prod.

Questions This Video Answers

  • How do I set up Firebase Firestore with global deny rules for a multi-tenant HubSpot backend?
  • What are best practices for testing Firebase Functions and Firestore locally with emulators?
  • How can I manage dev/prod aliases in Firebase RC for a multi-project deployment?
  • What is the role of an npmrc file when using private registries in Firebase projects?
  • How do you structure a multi-tenant data model in Firestore for HubSpot apps?
FirebaseHubSpot ExtensionsFirestoreCloud FunctionsEmulatorsCORSMulti-tenant ArchitectureDevOps/DeploymentEnvironment Managementnpmrc
Full Transcript
Okay, today we're going to take a step back from the HubSpot side of things and focus more on the well the back end. Our UI extensions can only do a couple things. They run in a sandbox, so they can only call the HubSpot APIs and well, our back end, and we're about to create that. If we want creator console to talk to YouTube, store ooth tokens, or run a background sync, we need that back end. In this episode, we're going to scaffold that back end on Firebase. By the end of this video, we'll enable Firebase on both our dev and prod Google Cloud projects and initialize dev backend. Create Fire Store with a global deny all rule and deploy it. Set up a multi-tenant path layout. Build two shared backend utilities every endpoint will use. Run the local emulators and deploy a health endpoint dev and prod with a Firebase RC aliases controlling which project we hit. Things are starting to get a little interesting. Hope you have your coffee. Let's dive in. Now, this is a step the CLA won't do for us. We have two Google Cloud projects from episode 1, demo CCD and demo CC prod, but Firebase isn't enabled on either one yet. So, before we touch the CLI, we're going to have to head over to the Firebase console and add Firebase to both projects. Open up your browser of choice and head to Firebase console, which is console.firebase.google.com. And we're going to hit get started by setting up a Firebase project. We can start from scratch here, but we actually already have a Google Cloud project. So, we're going to click that. Click that. And we're going to start with a demo CCD dev project. You'll notice that we now have a unique name for it. Continue through all this. Pay as you go. And we do not need Google Analytics for this project. All right, number one is down. Let's do rinse and repeat for the second one. All right, both projects are now Firebase enabled. And while we're here, let's go and get those project IDs and fill them into our uh system dependencies file. We're in the demo CC prod Firebase project. Now, let's go to settings, then general. Here, you're going to see the project ID from uh your Google Cloud project as well. But right now, we're looking for this one right here. Let's go ahead and select that. Copy. Let's go in here. And you can see here the Google plug cloud project ID is the Mindful Acre. and we're going to go down here to Firebase. We're looking at the prod value. So, we're going to type it in here. There we go. Now, let's go back and get the dev project ID up here. You can click on that demo ccde dev and double click on that. Grab it. Bring it over here. Save that. All right. Now, let's head back over to the terminal and we're going to log in and run the nit command from the Firebase CLI. Now, keep in mind if you did not have the Firebase CLI installed, go back to the first video where we actually installed it. So, again, in the terminal, we're going to go to Firebase login. I'm already logged in, so don't need to do anything. Then, we're going to type in Firebase and it we need Fire Store, we need functions, and we need emulators. Collect all those with the space bar. Hit enter after that. Uh this is where your fire store database is going to be set up. Uh you want this to be as close geographically to the area that you plan on serving. I am going to be selecting way down here US central and we'll save the defaults for the fire store rules. We'll save the defaults for fire store indexes. We're going in TypeScript. We do not need eslint right now. I'll be setting something up later on. And here's where you will install your dependencies. Now, uh, my company has an internal registry. So, if I hit yes on this, it's going to fail. You can go ahead and do this. It's going to go run through the entire thing. Take a few minutes. Um, if you have a similar situation that I do, I will show you how I get around that. So, from now, I'm going to hit N on this for myself. And we're going to set the emulators. At this point, we need a functions emulator and a fire store emulator. We're going to select the defaults for each. We do want to enable the UI for the emulator and we'll go do whatever is available. And you know what? What the heck? Why not download it now? And I bet you are using an AI coding agent. So, hit yes. All right, you are configured. You can see here that we have some extra files that have been made. We have a functions folder. We're actually going to rename that to backend functions. And in the Firebase.json file, you'll see where it says source is function. This needs to not be backend functions. Cool. I want to clear all this out. Now, during setup, there's a possibility that your fire store was actually set up. Often times, this does not happen. So, let's just go check. Go here. We're in the console. All right. We're going to go to the dev console and we're going to click on fire store here as I have a shortcut already. If you don't, just go database and storage and click on fire store here. And looks like nothing has been deployed. It says I can create a database. So back in the terminal, we're going to type in Firebase deploy dash only fires store. And that's doing its thing. And we should have a new fire store database set up. And here we can click from here, but it's already there. So just going to click on that and hit refresh on this. And we should see a database show up. Boom. All right. Head back over to your IDE. And we are going to the Fire Store rules folder, which a whole bunch of stuff commented out. I'm going to copy and paste in some stuff here. Hit save on that. Now, this is a more restrictive than minimum uh rule set. A path scoped rule says match/artifact/ dot dot dot would also work, but it leaves the door open the moment you add a new collection, forget to lock it down. A global deny all means every collection is blocked unless you explicitly open it through a cloud function using the admin SDK. All right, now that we have updated those rules, let's go ahead and go back to the CLI and we're going to deploy those rules to our newly created database. Firebase deploy-only fire store rules. Now if you look at the output you can see here that we have rules files as compiled successfully. So and we've uploaded them. So we're good to go. The dev database is now live locked down by default and the functions and the rest of the deploy mechanics are coming shortly. Now if you remember earlier when I was in uh going through the init process I did not do the npm install installing the dependencies. So we're going to add an npmrc file in there that will show it to go to the public npm registry. So in our backend functions here, we're going to rightclick, new file, type in npmrc. All right. In that file, we're going to copy and paste this in. And this just says the registry is located here. And we're using legacy pure dependencies. True. Again, you might not need this file, but it's not going to hurt anything. And if you're working with someone else, a colleague that actually has these restrictions, adding this in there is going to help them out in the end. Now that I have this in here, I can go ahead and install all my dependencies. I need to change over to my backend folder. So, cd backend functions. And we are going to run npm install. Great. And we're also going to do npm install axios. We're installing axios now. So, in the next video, we don't have a dependency management thing we have to do. This is the HTTP client every endpoint will reach for once OOTH lands. Now before we write any endpoint code, we need to make two utility files. Every cloud function we build uses both. Centralizing them now means we never paste the same path string twice or forget a course header. Now the data itself goes into a multi-tenant layout. One creator console app serves many HubSpot portals. So every document path is keyed by app ID first, portal ID second. One app, many portals, each portal with its own tokens. We lean on the structure for every endpoint we build from here on out. In our backend functions folder under source, we're going to create a folder called utils. And within that, we are going to create a file called fire store-path.ts. And then you're going to copy and paste this in. Every function that reads or writes tokens calls one of these. One typo in a path would break everything. Now you fix it in one place. The last two paths are intentionally different. Content studio project and content studio settings sit outside the artifact tree because the data is shared across app instances and doesn't need the app ID layer. Next file we're going to create same place is going to be middleware.ts.ts. Copy and paste this in. The HubSpot UI extensions calls your Firebase functions cross origin. Without Coors headers on the response, every request fails and the failure looks like a network error was pretty darn frustrating, not a missing header. So with Coors wraps every public endpoint, sets three headers, handles the options pre-flight in one place, uh, with air handling logs the air and returns a clean 500 error instead of leaking a stack trace. Next, let's head over to our TS config file. And underneath true, we're going to put this in use unknown in catch variables equals false. The reason we do this type 4.4 with strict mode types catch variables as unknown. That forces a a narrowing dance every time you want an error message. Setting use unknown and catch variables to false keeps the old any behavior. For cloud function code, that's the right trade-off. The alternative is an if air instance error check on every single handler. Now both rappers exist now. So the smallest endpoint we can ship the /halth route will deploy already gets to use them. All right. Now let's go ahead and move over to this index.ts file. You'll see here this is some boiler plate that was written like kind of a hello world type thing. We're going to replace it entirely and then we got to add our middleware patterns that we created earlier. sort of with cores and with air handling. Now, the uh with air handling on the outside and then with cores on the inside, errors get logged and then turned into a clean 500 before they ever even reach the cores layer. Every endpoint we add from here on follows the same wrapping order. Okay, let's go ahead and test our test out our emulator. We want to make sure we're in the backend functions folder. So we hit pwd. The reason why we want to be there is because if we go to backend functions, we're looking for this package.json file which is going to run our command. Uh if you look here, mpm runs serve actually runs the functions. So what I'm going to do, I just want to duplicate that. I'm going to do a serve dashall and then I'm going to remove this. This way it's going to run both the Firebase and the functions emulator. So if we go mpm run serve d-all our emulators are starting and in a second we should see our emulators. So if we want just click on that we'll just see easily see the port numbers for each. All right, emulator is on port 8080 for fire store and the functions is at 51 which is what we all set up. If you do not see that, you're going to need to go back into your Firebase config file right here and make sure you have these set to those numbers. Okay, great. Let's actually uh check that health endpoint we created. If you look back, where am I? You'll see that we created this endpoint uh called health here that we are going to it's just you know the most basic simple thing you can make. Uh so first in here when we fire it up our emulators we should see a function and here is the path to it and then we're going to create open up a new terminal window. All right, we're going to type in in our new terminal curl and then that URL that we had here. This is the ID. Let's see if I can find that in our root. And there it is right here in our dev value for that. If I run this now, I should get an okay response with a timestamp and the version number. We are good to go, folks. There. Now that really is the smallest possible endto-end signal that an emulator stack is alive. From here, every endpoint we add follows the same pattern. All right, it's time to deploy. Dev already has this fire store database. What's left is functions for both projects and fire store for prod. First, we wire up the alias map so we can switch between projects deliberately. Now, Firebase init created Firebase RC with just default project. Let's open that up. Let's close some of this stuff real quickly and find that Firebase RC. We don't need this right now. All right. Now, you can see here our default goes to that dev project. Now, what we're going to do is explicitly write out dev and prod aliases in here. So, I'm just going to copy and paste this and walk through it real quickly. So we can see here now that I've the default if we don't add anything to the end of our uh CLI command, it's going to default to the our development version. If we do explicitly call it out, it goes there. And if we explicitly call it prod, it's going to go to this. Now, keep in mind, these are my IDs for my Google Cloud uh project. So yours will obviously be different. So don't paste my stuff in there. Get your own stuff and put them in there yourself. Let's go ahead and uh tear down this emulator right now. Let's clear it out. Don't need it. Open. And we're going to type in Firebase uh use dev. And this is bothering me. So, I'm going to get rid of this. Start over. Copy all this out. So, I have one terminal open. Firebase use dev. All right. So, we chose dev here. Right now, we're going to deploy our function that we just created. Firebase deploy dash only space functions. Once this runs, you'll get back a functional URL keyed off the dev product project ID. For me, that's going to be ah interesting. Okay, looks like we have a failure here because we have a missing permission. So, let's go ahead and go to this log here and enable. Cool. It is enabled. Um, and this is up to you your cleanup here for this. I'm just going to go and set one. All right, let's rerun that deploy. All right, great. That actually worked. Uh, let's go ahead and move on to our prod version of it. Uh, this is going to be starting from scratch. So what we're going to do here is type in Firebase use produce over and now we're going to do Firebase deploy fire store, cool. We now have both all our functions and our fires uh deployed to both our prod and development environments. We could actually go into here and see if this runs. Look at that. Okay, that's exactly what we had on our environment here when we did that curl. All right, our last setup. Firebase functions autoloads the env.dev orprod based on the active alias. So the same code reads the right secrets without any conditional logic. We scaffold both end files. Now most values are placeholders and we'll fill in as the series creates those credentials. Go to backend functions folder. Create a new file called env.dev. And I'm going to copy and paste this in here. And you'll see all the stuff that you need to add here. I'm going to leave this uh as is for now. As a matter of fact, you know what? I'm going to copy this and make a template version of it for you rename template. And we're going to do the same thing for a prod version. Create a new file. Env.pro.template. So you're going to take these files, remove the template part of it, fill it in with your credentials here. You replace this with your location that you set when you did the initialization. And we'll get to the rest of this later on. Now, you don't want to put your G-Cloud project in your environment file. Cloud function set that at runtime. If you were to manually define it here, it's going to override the runtime values and break the Firebase use switch. All right, the back end scaffold fire store locked down with a deny all baseline and live in both dev and prod. Two shared utilities ready for every feature endpoint. Alias configured health deployed in dev and prod. Sign two committed and push

Get daily recaps from
HubSpot Developers

AI-powered summaries delivered to your inbox. Save hours every week while staying fully informed.