Deploy flows using Cloud Functions for Firebase

Cloud Functions for Firebase has an onCallGenkit method that lets you quickly create a callable function with a Genkit action (e.g. a Flow). These functions can be called using genkit/beta/clientor the Functions client SDK, which automatically adds auth info.

Before you begin

  • You should be familiar with Genkit's concept of flows, and how to write them. The instructions on this page assume that you already have some flows defined, which you want to deploy.
  • It would be helpful, but not required, if you've already used Cloud Functions for Firebase before.

1. Set up a Firebase project

If you don't already have a Firebase project with TypeScript Cloud Functions set up, follow these steps:

  1. Create a new Firebase project using the Firebase console or choose an existing one.

  2. Upgrade the project to the Blaze plan, which is required to deploy Cloud Functions.

  3. Install the Firebase CLI.

  4. Log in with the Firebase CLI:

    firebase login
    firebase login --reauth # alternative, if necessary
    firebase login --no-localhost # if running in a remote shell
  5. Create a new project directory:

    export PROJECT_ROOT=~/tmp/genkit-firebase-project1
    mkdir -p $PROJECT_ROOT
  6. Initialize a Firebase project in the directory:

    cd $PROJECT_ROOT
    firebase init genkit

    The rest of this page assumes that you've decided to write your functions in TypeScript, but you can also deploy your Genkit flows if you're using JavaScript.

2. Wrap the Flow in onCallGenkit

After you've set up a Firebase project with Cloud Functions, you can copy or write flow definitions in the project’s functions/src directory, and export them in index.ts.

For your flows to be deployable, you need to wrap them in onCallGenkit. This method has all the features of the normal onCall. It automatically supports both streaming and JSON responses.

Suppose you have the following flow:

const generatePoemFlow = ai.defineFlow(
  {
    name: "generatePoem",
    inputSchema: z.string(),
    outputSchema: z.string(),
  },
  async (subject: string) => {
    const { text } = await ai.generate(`Compose a poem about ${subject}.`);
    return text;
  }
);

You can expose this flow as a callable function using onCallGenkit:

import { onCallGenkit } from 'firebase-functions/https';

export generatePoem = onCallGenkit(generatePoemFlow);

Define an authorization policy

All deployed flows, whether deployed to Firebase or not, should have an authorization policy; without one, anyone can invoke your potentially-expensive generative AI flows. To define an authorization policy, use the authPolicy parameter of onCallGenkit:

export const generatePoem = onCallGenkit({
  authPolicy: (auth) => auth?.token?.email_verified,
}, generatePoemFlow);

This sample uses a manual function as its auth policy. In addition, the https library exports the signedIn() and hasClaim() helpers. Here is the same code using one of those helpers:

import { hasClaim } from 'firebase-functions/https';

export const generatePoem = onCallGenkit({
  authPolicy: hasClaim('email_verified'),
}, generatePoemFlow);

Make API credentials available to deployed flows

Once deployed, your flows need some way to authenticate with any remote services they rely on. Most flows need, at a minimum, credentials for accessing the model API service they use.

For this example, do one of the following, depending on the model provider you chose:

Gemini (Google AI)

  1. Make sure Google AI is available in your region.

  2. Generate an API key for the Gemini API using Google AI Studio.

  3. Store your API key in Cloud Secret Manager:

    firebase functions:secrets:set GOOGLE_GENAI_API_KEY

    This step is important to prevent accidentally leaking your API key, which grants access to a potentially metered service.

    See Store and access sensitive configuration information for more information on managing secrets.

  4. Edit src/index.ts and add the following after the existing imports:

    import {defineSecret} from "firebase-functions/params";
    const googleAIapiKey = defineSecret("GOOGLE_GENAI_API_KEY");
    

    Then, in the flow definition, declare that the cloud function needs access to this secret value:

    export const generatePoem = onCallGenkit({
      secrets: [googleAIapiKey]
    }, generatePoemFlow);
    

Now, when you deploy this function, your API key is stored in Cloud Secret Manager, and available from the Cloud Functions environment.

Gemini (Vertex AI)

  1. In the Cloud console, Enable the Vertex AI API for your Firebase project.

  2. On the IAM page, ensure that the Default compute service account is granted the Vertex AI User role.

The only secret you need to set up for this tutorial is for the model provider, but in general, you must do something similar for each service your flow uses.

Add App Check enforcement

Firebase App Check uses a built-in attestation mechanism to verify that your API is only being called by your application. onCallGenkit supports App Check enforcement declaratively.

export const generatePoem = onCallGenkit({
  enforceAppCheck: true,
  // Optional. Makes App Check tokens only usable once. This adds extra security
  // at the expense of slowing down your app to generate a token for every API
  // call
  consumeAppCheckToken: true,
}, generatePoemFlow);

Set a CORS policy

Callable functions default to allowing any domain to call your function. If you want to customize the domains that can do this, use the cors option. With proper authentication (especially App Check), CORS is often unnecessary.

export const generatePoem = onCallGenkit({
  cors: 'mydomain.com',
}, generatePoemFlow);

Complete example

After you've made all of the changes described earlier, your deployable flow looks something like the following example:

import { genkit } from 'genkit';
import { onCallGenkit, hasClaim } from 'firebase-functions/https';
import { defineSecret } from 'firebase-functions/params';

const apiKey = defineSecret("GOOGLE_GENAI_API_KEY");

const generatePoemFlow = ai.defineFlow({
  name: "generatePoem",
  inputSchema: z.string(),
  outputSchema: z.string(),
}, async (subject: string) => {
  const { text } = await ai.generate(`Compose a poem about ${subject}.`);
  return text;
});

export const generateFlow = onCallGenkit({
  secrets: [apiKey],
  authPolicy: hasClaim("email_verified"),
  enforceAppCheck: true,
}, generatePoemFlow);

3. Deploy flows to Firebase

After you've defined flows using onCallGenkit, you can deploy them the same way you would deploy other Cloud Functions:

cd $PROJECT_ROOT
firebase deploy --only functions

You've now deployed the flow as a Cloud Function! But you can't access your deployed endpoint with curl or similar, because of the flow's authorization policy. The next section explains how to securely access the flow.

Optional: Try the deployed flow

To try out your flow endpoint, you can deploy the following minimal example web app:

  1. In the Project settings section of the Firebase console, add a new web app, selecting the option to also set up Hosting.

  2. In the Authentication section of the Firebase console, enable the Google provider, used in this example.

  3. In your project directory, set up Firebase Hosting, where you will deploy the sample app:

    cd $PROJECT_ROOT
    firebase init hosting

    Accept the defaults for all of the prompts.

  4. Replace public/index.html with the following:

    <!DOCTYPE html>
    <html>
      <head>
        <title>Genkit demo</title>
      </head>
      <body>
        <div id="signin" hidden>
          <button id="signinBtn">Sign in with Google</button>
        </div>
        <div id="callGenkit" hidden>
          Subject: <input type="text" id="subject" />
          <button id="generatePoem">Compose a poem on this subject</button>
          <p id="generatedPoem"></p>
        </div>
        <script type="module">
          import { initializeApp } from "https://www.gstatic.com/firebasejs/11.0.1/firebase-app.js";
          import {
            getAuth,
            onAuthStateChanged,
            GoogleAuthProvider,
            signInWithPopup,
          } from "https://www.gstatic.com/firebasejs/11.0.1/firebase-auth.js";
          import {
            getFunctions,
            httpsCallable,
          } from "https://www.gstatic.com/firebasejs/11.0.1/firebase-functions.js";
    
          const firebaseConfig = await fetch("/__/firebase/init.json");
          initializeApp(await firebaseConfig.json());
    
          async function generatePoem() {
            const poemFlow = httpsCallable(getFunctions(), "generatePoem");
            const subject = document.querySelector("#subject").value;
            const response = await poemFlow(subject);
            document.querySelector("#generatedPoem").innerText = response.data;
          }
    
          function signIn() {
            signInWithPopup(getAuth(), new GoogleAuthProvider());
          }
    
          document.querySelector("#signinBtn").addEventListener("click", signIn);
          document
            .querySelector("#generatePoem")
            .addEventListener("click", generatePoem);
    
          const signinEl = document.querySelector("#signin");
          const genkitEl = document.querySelector("#callGenkit");
    
          onAuthStateChanged(getAuth(), (user) => {
            if (!user) {
              signinEl.hidden = false;
              genkitEl.hidden = true;
            } else {
              signinEl.hidden = true;
              genkitEl.hidden = false;
            }
          });
        </script>
      </body>
    </html>
    
  5. Deploy the web app and Cloud Function:

    cd $PROJECT_ROOT
    firebase deploy

Open the web app by visiting the URL printed by the deploy command. The app requires you to sign in with a Google account, after which you can initiate endpoint requests.

Optional: Run flows in the developer UI

You can run flows defined using onCallGenkit in the developer UI, exactly the same way as you run flows defined using defineFlow, so there's no need to switch between the two between deployment and development.

cd $PROJECT_ROOT/functions
npx genkit start -- npx tsx --watch src/index.ts

or

cd $PROJECT_ROOT/functions
npm run genkit:start

You can now navigate to the URL printed by the genkit start command to access.

Optional: Developing using Firebase Local Emulator Suite

Firebase offers a suite of emulators for local development, which you can use with Genkit.

To use the Genkit Dev UI with the Firebase Emulator Suite, start the Firebase emulators as follows:

npx genkit start -- firebase emulators:start --inspect-functions

This command runs your code in the emulator, and runs the Genkit framework in development mode. This launches and exposes the Genkit reflection API (but not the Dev UI).

To see traces from Firestore in the Dev UI, you can navigate to the Inspect tab and toggle the Dev/Prod switch. When toggled to prod it loads traces from firestore.