Run custom server-side scripts before and after requests


You can run your own custom server-side scripts before and after every request that your app sends to the Gemini API via Firebase AI Logicwithout changing your client code. You implement these scripts as callback-style functions deployed to Cloud Functions for Firebase.

With this capability, you can do things like moderate prompts, cap token usage, log generations for analytics, or redact response content.

Two event types are available:

  • beforeGenerateContent: Runs before a request reaches the Gemini API. The function can inspect or modify the request, or block the request entirely by throwing an error.

  • afterGenerateContent: Runs after the response is sent back from the Gemini API and before it's returned to the client app. The function can inspect or modify the response, block the response entirely, or just observe it (like for logging or auditing).

Once your scripts are deployed as functions to Cloud Functions for Firebase, they're registered as Firebase AI Logic triggers, which means they'll run for every generateContent request in your project to the Gemini API via Firebase AI Logic (including requests made with server prompt templates).

These functions are not triggered by requests made to the Gemini API that aren't through Firebase AI Logic.

Prerequisites

Step 1: Set up your project for Cloud Functions for Firebase

If you've never used Cloud Functions for Firebase in your Firebase project, then complete the following setup.

  1. Make sure that your Firebase project is on the pay-as-you-go Blaze pricing plan (required to use Cloud Functions for Firebase).

  2. Install command-line interfaces (CLIs): gcloud CLI and Firebase CLI

  3. Grant the default compute service account the Cloud Build Service Account role (roles/cloudbuild.builds.builder) it needs to build your function. Run the following gcloud CLI command:

    gcloud projects add-iam-policy-binding PROJECT_ID \
      --member="serviceAccount:PROJECT_NUMBER-compute@developer.gserviceaccount.com" \
      --role="roles/cloudbuild.builds.builder"
    
  4. Initialize Cloud Functions for Firebase in your Firebase project:

    1. Run the following Firebase CLI command:

      firebase init functions
      
    2. When prompted, choose TypeScript.

    3. Ensure that firebase-functions in your functions/package.json is version 6.3.0 or later. Here's how to check your version:

      npm --prefix functions list firebase-functions
      

Step 2: Write your functions

Write a pre-request function (beforeGenerateContent) Write a post-request function (afterGenerateContent)

Write a pre-request function (beforeGenerateContent)

With the beforeGenerateContent event type, the function is triggered when the Firebase AI Logic proxy receives a generateContent request. The function runs against the request before the request is sent to the Gemini API. The function can modify the request or block the request completely.

Make sure you review the following information before writing your function:

Example

Here's an example pre-request function that does the following:

  • Specifies that the function should only run when the request is for a specific Gemini API provider.

  • Inspects the prompt for blocked topics and rejects the request by throwing an error.

  • Caps the maximum output tokens for text-generating models.

import { logger } from "firebase-functions";
import {
  beforeGenerateContent,
  HttpsError,
  vertexV1Beta1,
  type VertexV1Beta1GenerateContentRequest,
} from "firebase-functions/v2/ai";

const BLOCKED_TOPICS = ["weapon", "explosive", "self-harm"];
const MAX_OUTPUT_TOKENS = 4000;

export const guardPrompts = beforeGenerateContent((event) => {
  // 1. Optional: If you want the function to only run for a specific Gemini API provider, specify it here.
  if (event.data.api !== vertexV1Beta1) return;
  const request = event.data.request as VertexV1Beta1GenerateContentRequest;

  // 2. Read the prompt: contents[] -> parts[] -> text
  const prompt = (request.contents ?? [])
    .flatMap((c) => c.parts ?? [])
    .map((p) => ("text" in p ? p.text : "") ?? "")
    .join(" ")
    .toLowerCase();

  // 3. Throwing rejects the request. The request is never sent to the Gemini API.
  const blocked = BLOCKED_TOPICS.find((t) => prompt.includes(t));
  if (blocked) {
    logger.warn("Blocked a prompt", { topic: blocked });
    throw new HttpsError("invalid-argument", `We don't return content about ${blocked}.`);
  }

  logger.info("Allowing generation", {
    model: event.data.model,
    authType: event.authType,
    authId: event.authId,
    appId: event.appId,
  });

  // 4. The next step truncates the response, but that will break images.
  if (event.data.model.includes("image")) return;

  // 5. Return the WHOLE request, edited. Returning nothing leaves it untouched.
  return {
    ...request,
    generationConfig: {
      ...request.generationConfig,
      maxOutputTokens: Math.min(
        request.generationConfig?.maxOutputTokens ?? MAX_OUTPUT_TOKENS,
        MAX_OUTPUT_TOKENS,
      ),
    },
  };
});

Key considerations for pre-request functions

  • Specify the Gemini API provider: event.data.request can be for either the Gemini Developer API or the Agent Platform Gemini API (formerly Vertex AI). The request objects for these different APIs have different shapes. To safely work with the request object, you must check event.data.api (for example, compare to geminiV1Beta or vertexV1Beta1, respectively).

  • Throwing blocks the request: If you throw an HttpsError, the request will be rejected.

  • Return the entire request: If your function modifies the request, then you must return the complete, modified request object. Returning nothing (or undefined) leaves the request unchanged.

  • Test for latency: Depending on what your function does, it could add latency and impact the user experience.

Write a post-request function (afterGenerateContent)

With the afterGenerateContent event type, the function is triggered when the Firebase AI Logic proxy receives a response from a generateContent request. The function runs against the response before the response is returned to the client app. The function can log usage, modify the response, or block the response completely.

Make sure you review the following information before writing your function:

Example

Here's an example post-request function that logs token usage and the finish reason:

import { logger } from "firebase-functions";
import {
  afterGenerateContent,
  vertexV1Beta1,
  type VertexV1Beta1GenerateContentResponse,
} from "firebase-functions/v2/ai";

export const recordGenerationUsage = afterGenerateContent((event) => {
  // Optional: If you want the function to only run for a specific Gemini API provider, specify it here.
  if (event.data.api !== vertexV1Beta1) return;
  const response = event.data.response as VertexV1Beta1GenerateContentResponse;

  logger.info("Generation finished", {
    model: event.data.model,
    promptTokens: response.usageMetadata?.promptTokenCount,
    totalTokens: response.usageMetadata?.totalTokenCount,
    finishReason: response.candidates?.[0]?.finishReason,
  });

  // To leave the response untouched, return nothing.
  // To modify the response, return a modified response object here.
});

Key considerations for post-request functions

  • Specify the Gemini API provider: event.data.response can be for either the Gemini Developer API or the Agent Platform Gemini API (formerly Vertex AI). To safely cast and work with the request object, you must check event.data.api (for example, compare to geminiV1Beta or vertexV1Beta1, respectively).

  • Test for latency: Depending on what your function does, it could add latency and impact the user experience.

Step 3: Deploy your functions

Deploying your functions to Firebase grants the Firebase AI Logic service agent permission to invoke these functions, and registers each function as a Firebase AI Logic trigger.

  1. Deploy your functions using the Firebase CLI:

    firebase deploy --only functions
    
  2. After deploying, confirm your functions have been deployed to Firebase:

    firebase functions:list
    
  3. If you need to iterate on your function:

    Update the function in your project directory, and then run firebase deploy --only functions again.

Stop a function from running

To stop one of these functions from running, it must be deleted from our servers and unregistered as a Firebase AI Logic trigger. You can do this using the Firebase CLI with either of the following options:

  • Option 1: Delete the function implicitly

    1. Remove the function from your project directory codebase.

    2. Run the following Firebase CLI command:

      firebase deploy --only functions
      
  • Option 2: Delete the function explicitly

    1. Remove the function from your project directory codebase.

    2. Run the following Firebase CLI command:

      firebase functions:delete FUNCTION_NAME
      



Event data reference

Both beforeGenerateContent and afterGenerateContent receive an AIBlockingEvent object containing context and metadata about the request.

Top-level request metadata (AIBlockingEvent)

The top-level AIBlockingEvent object provides information about the caller and the triggering environment:

  • event.authType: Authentication state for the caller: "app_user", "unauthenticated", or "unknown".
  • event.authId: The caller's Firebase Authentication UID, if signed in.
  • event.authClaims: The caller's custom auth claims, if any.
  • event.appId: The Firebase App ID that made the request.
  • event.androidPackageName / event.iosBundleId: The package name or bundle ID of the calling app (applicable for Android or Apple platforms, respectively).
  • event.data: The event payload, which differs between pre-request and post-request functions:

Pre-request event data (beforeGenerateContent)

In a beforeGenerateContent function, event.data is populated with a BeforeGenerateContentData object:

  • event.data.api: The Gemini API provider: geminiV1Beta (Gemini Developer API) or vertexV1Beta1 (Agent Platform Gemini API (formerly Vertex AI)).
  • event.data.model: The full model resource path (for example, projects/{PROJECT_ID}/locations/global/publishers/google/models/gemini-3.8-flash).
  • event.data.template: Metadata about the server prompt template used (PromptTemplateInfo), if applicable.
  • event.data.request: The outgoing request payload. The object type and properties depend on the Gemini API provider:

Post-request event data (afterGenerateContent)

In an afterGenerateContent function, event.data is populated with an AfterGenerateContentData object. This object extends BeforeGenerateContentData (providing api, model, template, and request), and adds the model's response:



Limitations and behaviors

When implementing these functions, keep the following behaviors and limitations in mind:

  • generateContent requests only: These functions can only be triggered by generateContent requests to the Gemini API via Firebase AI Logic.

    The following will not trigger these functions and the functions will be bypassed silently for that request:

    • Requests to generateContentStream will not trigger these functions.

    • Requests to the Gemini Live API will not trigger these functions.

  • No client-side code changes: Other than ensuring that you use generateContent requests when you want to run these functions, no changes are required in your client-side codebase.

    These functions are deployed to our servers, and they're registered as Firebase AI Logic triggers so that the Firebase AI Logic proxy can intercept requests and responses server-side.

  • Project-level scope: You can deploy at most one beforeGenerateContent function and one afterGenerateContent function per Firebase project.

  • Default locations: These functions will be deployed to us-central1 by default (learn about locations for functions). However, the function will be registered as a Firebase AI Logic trigger in the global region regardless of where you deploy your function.