DEV Community

Cover image for Firebase AI Logic in Angular: Client-Side Gemini Without a Custom Backend

Firebase AI Logic in Angular: Client-Side Gemini Without a Custom Backend

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+.


Why Firebase AI Logic in web apps

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:

  • Shopping assistants, support chat, on-page copilots
  • Multi-turn chat and function calling (tools that call your existing Angular services)
  • Features where latency matters; inference starts from the client without an extra hop through your server

When to use something else:

  • Sensitive prompts or secrets: keep those on the server (Genkit, Cloud Functions)
  • Heavy RAG over private corpora: server-side retrieval is usually safer and more flexible
  • Billing/abuse control at the edge: combine App Check enforcement with Firebase Console monitoring; add server gates for high-risk actions

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.


What you are building

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()
Enter fullscreen mode Exit fullscreen mode

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).


Prerequisites

  • Angular 18+ (standalone, ApplicationConfig style)
  • Node.js LTS
  • A Google account and a Firebase project
  • firebase >= 12.19.0 (required for current Gemini + App Check behaviour)
npm install firebase@^12.19.0
Enter fullscreen mode Exit fullscreen mode

Part 1: Firebase Console (do this before writing any code)

1. Create a project and web app

  1. Firebase ConsoleCreate a new Firebase project
  2. Add appWeb (</>)
  3. Copy the firebaseConfig object — you will paste it in Part 2

2. Enable Firebase AI Logic (early)

  1. AI ServicesAI LogicGet started
  2. Choose Gemini Developer API and finish the setup wizard. Ensure to enable the APIs recommended and AI monitoring.

Wait 5–10 minutes, then in Google Cloud console (same project ID as Firebase): APIs & ServicesEnabled 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.

3. reCAPTCHA Enterprise + App Check

3a. Create a reCAPTCHA Enterprise key

  1. Google Cloud (same project) → SecurityFraud Defense → Enable reCAPTCHA Enterprise API
  2. Select Keys on the tab → Create key → Input any Display name of your choice → Website
  3. Domains: add domains that production only. Do not add localhost. For example:
    • <project-id>.web.app
    • <project-id>.firebaseapp.com
    • Any custom domain you use later
  4. Click on Create Key
  5. After creation, copy the site key. This key starts with something like 6L...

3b. Register App Check

  1. Firebase Console → SecurityApp Check → Click on the Apps tab → Click on your web app → reCAPTCHA Enterprise → paste site key (from Step 3a). You can leave the default configurations as they are.
  2. APIs tab → Firebase AI Logic. You will find Monitoring and Baseline Protection have already been enabled (Basic - Enforced). However, you can set up additional checks if you want such as Replay Protection.

4. Browser API key restrictions

  1. Google Cloud → APIs & ServicesCredentials
  2. Open the Browser key matching your firebaseConfig.apiKey
  3. API restrictions: You will find about 25 APIs already allowed on that project and ensure you allow Firebase AI Logic API and Firebase App Check API if not already allowed.
  4. Application restrictions: None while developing on localhost

Part 2: Angular configuration

Step 1: Generate Angular environment variables files

You can generate your Angular environment files using the command:

ng generate environments
Enter fullscreen mode Exit fullscreen mode

Step 2: 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';
Enter fullscreen mode Exit fullscreen mode

Step 3: 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;
}
Enter fullscreen mode Exit fullscreen mode

Step 4: 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,
};
Enter fullscreen mode Exit fullscreen mode

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,
};
Enter fullscreen mode Exit fullscreen mode

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"
  }
]
Enter fullscreen mode Exit fullscreen mode

Always import the alias path in app code — never environment.development directly:

import { environment } from '../../environments/environment';
Enter fullscreen mode Exit fullscreen mode

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.

Step 5: 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);
    }),
  ]);
}
Enter fullscreen mode Exit fullscreen mode

Step 6: 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(),
  ],
};
Enter fullscreen mode Exit fullscreen mode

Step 7: Localhost debug token — 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>
Enter fullscreen mode Exit fullscreen mode

Step 8: Run locally and capture the debug token

ng serve
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

Copy the UUID (without quotes), then register it:

Firebase Console → App Check → Apps → your web app → menu → Manage debug tokensAdd 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.


Part 3: Your first generateContent

Step 1: 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();
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Call it from a component

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
Enter fullscreen mode Exit fullscreen mode
import { Component, inject, signal } from '@angular/core';
import { AiService } from '../services/ai.service';

@Component({
  selector: 'app-ai-demo',
  template: `
    <button (click)="run()" [disabled]="loading()">
      {{ loading() ? '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 loading = signal(false);
  readonly reply = signal('');
  readonly error = signal('');

  async run(): Promise<void> {
    this.loading.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.loading.set(false);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Render it from src/app/app.html:

<app-ai-demo />
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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


Part 4: Verify it worked

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.


Part 5: What you can add next

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.


Conclusion

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.


File checklist

  • [ ] src/environments/firebase.config.tsnew
  • [ ] src/environments/environment.model.tsnew
  • [ ] src/environments/environment.ts — production values
  • [ ] src/environments/environment.development.ts — add appCheckDebugToken
  • [ ] src/app/firebase/firebase-ai.tsnew
  • [ ] src/app/app.config.ts — add provideFirebaseAI()
  • [ ] src/index.html — localhost debug script
  • [ ] src/app/services/ai.service.tsnew
  • [ ] AiDemo component + import in app.ts

Related links

Top comments (0)