Since I last wrote Building AI-powered e-commerce applications using Angular & Firebase AI Logic (formerly Vertex AI in Firebase) in 2025, so much has changed that this article may look like a complete rewrite.
Firebase AI Logic is the successor to Vertex AI in Firebase (May 2025). This post covers the Angular setup only. For the full rename, new APIs, and migration from firebase/vertexai, see Vertex AI in Firebase is now Firebase AI Logic — What Actually Changed.
Official Firebase docs cover each piece, but the order matters. This guide follows the sequence that actually works. We use the modular firebase JS SDK + Angular DI, so this works on Angular 18+.
Most Gemini tutorials show either AI Studio + a raw API key or a custom Node/Python proxy you host and secure yourself. Firebase AI Logic sits in between: Gemini runs from your Angular app in the browser, but requests go through Firebase's managed path and not a DIY backend you maintain.
What you get on web:
| Benefit | Why it matters |
|---|---|
| No custom AI backend | Call generateContent() from a service; skip building Express/Cloud Functions just to hide an API key |
| App Check attestation | Proves traffic comes from your real app (reCAPTCHA Enterprise in prod, debug tokens on localhost) — required for production AI Logic on web |
| One Firebase project | Same Console for Hosting, Auth, Firestore, Remote Config, and AI monitoring |
| Gemini Developer API path | GoogleAIBackend() : fast onboarding, Spark plan for prototypes, Console wizard provisions APIs for you |
| Upgrade path | Switch to AgentPlatformBackend() later if you need enterprise Vertex/Agent Platform features; often a one-line backend change |
Good fit for web apps:
When to use something else:
For ByteWise, the agent reads inventory and updates the cart via function calling; still client-side AI Logic, with business logic in Angular services. This guide stops at your first generateContent(); the ByteWise repo goes further.
Firebase Console (AI Logic + App Check + API keys)
↓
firebase.config.ts ← credentials
firebase-ai.ts ← initializeApp + App Check + getAI (providers)
app.config.ts ← register providers once
ai.service.ts ← inject FIREBASE_AI → generateContent()
You only create two new Firebase-specific files beyond a standard Angular app:
| File | Purpose |
|---|---|
src/environments/firebase.config.ts |
firebaseConfig + reCAPTCHA site key |
src/app/firebase/firebase-ai.ts |
Bootstrap: tokens, App Check, provideFirebaseAI() |
Everything else is small edits to files you already have (app.config.ts, index.html, one service).
ApplicationConfig style)firebase >= 12.19.0
npm install firebase@^12.19.0
</>) firebaseConfig object — you will paste it in Part 2
Wait 5–10 minutes, then in Google Cloud console (same project ID as Firebase): APIs & Services → Enabled APIs & services → scroll past the charts to the API table → use Filter to confirm Firebase AI Logic API and Gemini API are listed. Firebase's wizard labels the second one Gemini Developer API; GCP now shows Gemini API (generativelanguage.googleapis.com). You may also see Gemini for Google Cloud API when filtering Gemini — that is for Gemini inside the Cloud Console and is not required for Firebase AI Logic in your Angular app.
Skipping this step is the most common cause of 403 "The caller does not have permission" later.
3a. Create a reCAPTCHA Enterprise key
localhost. For example:
<project-id>.web.app``<project-id>.firebaseapp.com`` 6L...
3b. Register App Check
Basic - Enforced). However, you can set up additional checks if you want such as firebaseConfig.apiKey
You can generate your Angular environment files using the command:
ng generate environments
src/environments/firebase.config.ts
Inside the environment folder, create a file with the name firebase.config.ts and copy the code snippet below. Replace the YOUR_ values with the actual values from your Firebase project Settings (scroll all the way down to the SDK setup and configuration section).
The recaptchaEnterpriseSiteKey is the same as the one in Part 1: Step 3a: Google Cloud (same project) → Security → Fraud Defense → Keys → Copy the already created key.
import type { FirebaseOptions } from 'firebase/app';
export const firebaseConfig: FirebaseOptions = {
apiKey: 'YOUR_API_KEY',
authDomain: 'YOUR_PROJECT.firebaseapp.com',
projectId: 'YOUR_PROJECT_ID',
storageBucket: 'YOUR_PROJECT.firebasestorage.app',
messagingSenderId: 'YOUR_SENDER_ID',
appId: 'YOUR_APP_ID',
};
export const recaptchaEnterpriseSiteKey = 'YOUR_RECAPTCHA_SITE_KEY';
src/environments/environment.model.ts
Create environment.model.ts — one shared type for production and development. appCheckDebugToken is optional so production omits it, while firebase-ai.ts can still reference environment.appCheckDebugToken without IDE TS2339 errors (your editor type-checks environment.ts, not the dev file Angular swaps in at build time).
import type { FirebaseOptions } from 'firebase/app';
export interface AppEnvironment {
production: boolean;
firebaseConfig: FirebaseOptions;
recaptchaEnterpriseSiteKey: string;
/** Dev/CI only — set in `environment.development.ts`, omit in `environment.ts` */
appCheckDebugToken?: boolean | string;
}
environment.ts and environment.development.ts
src/environments/environment.ts (production — used by ng build):
import { firebaseConfig, recaptchaEnterpriseSiteKey } from './firebase.config';
import type { AppEnvironment } from './environment.model';
export const environment: AppEnvironment = {
production: true,
firebaseConfig,
recaptchaEnterpriseSiteKey,
};
src/environments/environment.development.ts (local dev — swapped in by ng serve):
import { firebaseConfig, recaptchaEnterpriseSiteKey } from './firebase.config';
import type { AppEnvironment } from './environment.model';
export const environment: AppEnvironment = {
production: false,
firebaseConfig,
recaptchaEnterpriseSiteKey,
appCheckDebugToken: true,
};
Confirm angular.json has fileReplacements under the development build configuration ( ng generate environments usually adds this):
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.development.ts"
}
]
Always import the alias path in app code — never environment.development directly:
import { environment } from '../../environments/environment';
NOTE: On ng serve, the CLI substitutes environment.development.ts wherever you import environment.ts. Production builds use the real environment.ts with no debug token.
src/app/firebase/firebase-ai.ts
Create a file with the name firebase-ai.ts under a folder firebase.
import {
inject,
InjectionToken,
makeEnvironmentProviders,
PLATFORM_ID,
provideAppInitializer,
type EnvironmentProviders,
} from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import { getAI, GoogleAIBackend, type AI } from 'firebase/ai';
import { initializeApp, type FirebaseApp } from 'firebase/app';
import {
CustomProvider,
initializeAppCheck,
ReCaptchaEnterpriseProvider,
type AppCheck,
} from 'firebase/app-check';
import { environment } from '../../environments/environment';
export const FIREBASE_AI = new InjectionToken<AI>('FIREBASE_AI');
const FIREBASE_APP_CHECK = new InjectionToken<AppCheck>('FIREBASE_APP_CHECK');
const firebaseApp = initializeApp(environment.firebaseConfig);
function initAppCheck(firebaseApp: FirebaseApp, platformId: object): AppCheck {
const isBrowser = isPlatformBrowser(platformId);
if (isBrowser && !environment.production) {
(globalThis as typeof globalThis & { FIREBASE_APPCHECK_DEBUG_TOKEN?: boolean | string })
.FIREBASE_APPCHECK_DEBUG_TOKEN ??= environment.appCheckDebugToken ?? true;
}
if (!isBrowser) {
return initializeAppCheck(firebaseApp, {
provider: new CustomProvider({
getToken: async () => ({
token: 'ssr-placeholder',
expireTimeMillis: Date.now() + 60 * 60 * 1000,
}),
}),
isTokenAutoRefreshEnabled: false,
});
}
return initializeAppCheck(firebaseApp, {
provider: new ReCaptchaEnterpriseProvider(environment.recaptchaEnterpriseSiteKey),
isTokenAutoRefreshEnabled: true,
});
}
export function provideFirebaseAI(): EnvironmentProviders {
return makeEnvironmentProviders([
{
provide: FIREBASE_APP_CHECK,
useFactory: (platformId: object) => initAppCheck(firebaseApp, platformId),
deps: [PLATFORM_ID],
},
{
provide: FIREBASE_AI,
useFactory: (_appCheck: AppCheck): AI =>
getAI(firebaseApp, {
backend: new GoogleAIBackend(),
useLimitedUseAppCheckTokens: environment.production,
}),
deps: [FIREBASE_APP_CHECK],
},
// Eager-init App Check on startup — prints the debug token in DevTools
// immediately so you can register it in Firebase Console before Part 3.
provideAppInitializer(() => {
inject(FIREBASE_APP_CHECK);
}),
]);
}
src/app/app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { provideFirebaseAI } from './firebase/firebase-ai';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideFirebaseAI(),
],
};
src/index.html
Add this inside the <body> element, right before <app-root>.
<script>
if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') {
self.FIREBASE_APPCHECK_DEBUG_TOKEN = true;
}
</script>
ng serve
Open http://localhost:4200 (the URL the Angular CLI prints in the terminal).
Where the token appears: the Firebase SDK logs it when initializeAppCheck() runs with the debug provider active. Open DevTools → Console:
Search/filter for AppCheck debug token or debug token
You should see a line similar to:
Firebase App Check debug token: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
Copy the UUID (without quotes), then register it:
Firebase Console → App Check → Apps → your web app → ⋮ menu → Manage debug tokens → Add debug token → Give the debug token any name of your choice → paste → save → hard-refresh the app (Ctrl+Shift+R).
NOTE: The debug token is not in environment.ts. It's minted by the SDK and printed in the Console once App Check initializes on the client. The debug token is ONLY for use during development. Production must use reCAPTCHA, not debug tokens.
generateContent
src/app/services/ai.service.ts
import { inject, Injectable } from '@angular/core';
import { getGenerativeModel } from 'firebase/ai';
import { FIREBASE_AI } from '../firebase/firebase-ai';
@Injectable({ providedIn: 'root' })
export class AiService {
private readonly ai = inject(FIREBASE_AI);
async ask(prompt: string): Promise<string> {
const model = getGenerativeModel(this.ai, { model: 'gemini-3.5-flash' });
const result = await model.generateContent(prompt);
return result.response.text();
}
}
Generate a dedicated AI Demo component with the Angular CLI, or wire the call into your root App component.
ng generate component ai-demo --inline-template --skip-tests
js
import { Component, inject, signal } from '@angular/core';
import { AiService } from '../services/ai.service';
@Component({
selector: 'app-ai-demo',
template: `
<button (click)="run()" [disabled]="()">
{{ () ? 'Please wait…' : 'Ask Gemini' }}
</button>
@if (error()) {
<p role="alert">{{ error() }}</p>
}
@if (reply()) {
<pre>{{ reply() }}</pre>
}
`,
styleUrl: './ai-demo.scss',
})
export class AiDemo {
private readonly ai = inject(AiService);
readonly = signal(false);
readonly reply = signal('');
readonly error = signal('');
async run(): Promise<void> {
this..set(true);
this.error.set('');
this.reply.set('');
try {
this.reply.set(await this.ai.ask('Say hello in one sentence.'));
} catch (err) {
this.error.set(
err instanceof Error ? err.message : 'Something went wrong. Check the console.',
);
} finally {
this..set(false);
}
}
}
Render it from src/app/app.html:
<app-ai-demo />
Import AiDemo in src/app/app.ts — Angular components are standalone by default and must be listed in the parent imports array:
import { Component } from '@angular/core';
import { AiDemo } from './ai-demo/ai-demo';
@Component({
selector: 'app-root',
imports: [AiDemo],
templateUrl: './app.html',
styleUrl: './app.scss',
})
export class App {}
Without that import, <app-ai-demo /> will fail at compile time with an unknown element error.
Run ng serve and click on the Ask Gemini button in the rendered UI on http://localhost:4200
Click Ask Gemini. If you get text back — not a 403 — your setup works. That is the pass/fail check.
On first load, the console should show no App Check token fetch failed, and you should have registered the debug token from Part 2, Step 8. Optional: in DevTools → Network, filter firebasevertexai and confirm a POST to :generateContent returns 200. Do not worry if you cannot spot X-Firebase-AppCheck — the header is easy to miss, and Gemini succeeding is what matters.
403 The caller does not have permission? Re-run AI Logic → Get started, confirm the debug token is registered, confirm the Browser key allowlist includes Firebase AI Logic API + Firebase App Check API, then wait a few minutes and hard-refresh.
| Feature | SDK entry point |
|---|---|
| Multi-turn chat | startChat() on a generative model |
| Function calling | tools +FunctionCallingConfig on the model |
| Streaming | generateContentStream() |
| System instructions | systemInstruction in model config |
For a full function-calling example, see ByteWise ai.service.ts.
We are at an inflection point where developers can build intelligent web experiences, powered by Large Language Models. Firebase AI Logic provides this infrastructure with a free tier on the Gemini Developer API and Agent Platform Gemini API for enterprise-level use cases.
If you want these capabilities where latency matters, Firebase AI Logic will be your best bet, while offering you security and abuse prevention for your production apps.
To see Firebase AI Logic in action in e-commerce applications, check out Bytewise Shop, a fictional tech gadget shop I built to demonstrate the use of tool calling on actions such as add to cart, check inventory, clearing cart, all using natural language.
src/environments/environment.ts — production valuessrc/environments/environment.development.ts — add appCheckDebugToken
provideFirebaseAI()
AiDemo component + import in app.ts