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 Logic — without 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
Set up Firebase AI Logic: If you haven't already, complete the Firebase AI Logic getting started guide, which describes how to set up your Firebase project, connect your app to Firebase, add the SDK, initialize the backend service for your chosen Gemini API provider, and create a
GenerativeModelinstance.Required permissions: Make sure that you have the required IAM permissions for deploying to Cloud Functions for Firebase.
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.
Make sure that your Firebase project is on the pay-as-you-go Blaze pricing plan (required to use Cloud Functions for Firebase).
Install command-line interfaces (CLIs): gcloud CLI and Firebase CLI
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"Initialize Cloud Functions for Firebase in your Firebase project:
Run the following Firebase CLI command:
firebase init functionsWhen prompted, choose TypeScript.
Ensure that
firebase-functionsin yourfunctions/package.jsonis 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:
- Event data that you can use in your function
- Key considerations for pre-request functions
- Limitations and behaviors for functions
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.requestcan 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 checkevent.data.api(for example, compare togeminiV1BetaorvertexV1Beta1, 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:
- Event data that you can use in your function
- Key considerations for post-request functions
- Limitations and behaviors for functions
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.responsecan 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 checkevent.data.api(for example, compare togeminiV1BetaorvertexV1Beta1, 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.
Deploy your functions using the Firebase CLI:
firebase deploy --only functionsAfter deploying, confirm your functions have been deployed to Firebase:
firebase functions:listIf you need to iterate on your function:
Update the function in your project directory, and then run
firebase deploy --only functionsagain.
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
Remove the function from your project directory codebase.
Run the following Firebase CLI command:
firebase deploy --only functions
Option 2: Delete the function explicitly
Remove the function from your project directory codebase.
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:- For
beforeGenerateContent, this is aBeforeGenerateContentDataobject. - For
afterGenerateContent, this is anAfterGenerateContentDataobject.
- For
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) orvertexV1Beta1(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:- Gemini Developer API (
geminiV1Beta):GeminiV1BetaGenerateContentRequest - Agent Platform Gemini API (formerly Vertex AI) (
vertexV1Beta1):VertexV1Beta1GenerateContentRequest
- Gemini Developer API (
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:
event.data.response: The model's response payload. The object type and properties depend on the Gemini API provider:- Gemini Developer API (
geminiV1Beta):GeminiV1BetaGenerateContentResponse - Agent Platform Gemini API (formerly Vertex AI) (
vertexV1Beta1):VertexV1Beta1GenerateContentResponse
- Gemini Developer API (
Limitations and behaviors
When implementing these functions, keep the following behaviors and limitations in mind:
generateContentrequests only: These functions can only be triggered bygenerateContentrequests 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
generateContentStreamwill 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
generateContentrequests 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
beforeGenerateContentfunction and oneafterGenerateContentfunction per Firebase project.Default locations: These functions will be deployed to
us-central1by default (learn about locations for functions). However, the function will be registered as a Firebase AI Logic trigger in theglobalregion regardless of where you deploy your function.