---
title: "How to add feature flags and experiments to your app with Gemini CLI"
description: "Use Gemini CLI to add report-layout feature flags, verify fallback behavior, and track exposure after rendering the assigned layout."
canonical: "https://amplitude.com/en-us/build/gemini-cli-feature-flags"
---

# How to add feature flags and experiments to your app with Gemini CLI

Use Gemini CLI to add report-layout feature flags, verify fallback behavior, and track exposure after rendering the assigned layout.

Source: https://amplitude.com/en-us/build/gemini-cli-feature-flags

---

# How to add feature flags and experiments to your app with Gemini CLI

Use Gemini CLI to fetch a report-layout flag, render its assigned layout, and track an exposure only after the assigned report appears. Review the generated changes against a complete browser example and verify the result in Amplitude.

- Gemini CLI
- Vite
- Unified SDK 1.1.43
- 30 min

Chris Tam · Last updated 4 November 2026

Gemini CLI can edit the report layouts and their event handlers together. Amplitude [feature flags](https://amplitude.com/feature-management) assigns the layout, while [experimentation](https://amplitude.com/amplitude-experiment) connects the assigned experience to report exports.

In this tutorial, you'll prompt Gemini, review fallback and exposure behavior, run the report, and check both assignments in a development project.

A browser report app that lets you fetch a report-layout flag, render its assigned layout, and track an exposure only after the assigned report appears. The agent's code is reviewable against the complete files below.

## Prerequisites

- Gemini CLI installed and signed in. Use its interactive terminal session in the project folder and review proposed tools and file edits.
- Node.js 22.12 or later and Corepack with pnpm 10.33.2. The example pins Vite 8.3.4 and `@amplitude/unified` 1.1.43; it doesn't require another Build tutorial.
- A development Amplitude project and its public client API key from Settings > Projects > your project > General. Confirm its US or EU data center. For flags, also obtain a client deployment key from the same project in Amplitude Experiment.

[Sign up for Amplitude](https://app.amplitude.com/signup) if you need an account, and [compare plans](https://amplitude.com/pricing) for your project's allowances. Use synthetic data throughout this walkthrough.

## 1. Open the project and connect context

I use a small Vite app to make the generated changes easy to inspect. For a new example, create an empty repository. If you're extending an existing app, open its repository and preserve its setup instead.

Terminal

```sh
mkdir amplitude-report-feature-flags
cd amplitude-report-feature-flags
git init
```

Optionally connect [Amplitude MCP for Gemini CLI](https://amplitude.com/docs/amplitude-ai/amplitude-mcp/gemini-cli) for event context. Merge this entry into the project's `.gemini/settings.json`, preserving other settings and servers. For an EU project, use `https://mcp.eu.amplitude.com/mcp`.

.gemini/settings.json

```json
{
  "mcpServers": {
    "amplitude": {
      "httpUrl": "https://mcp.amplitude.com/mcp"
    }
```

Start `gemini` from this folder. Inside its interactive session, run `/mcp auth amplitude` and complete sign-in. Ask which Amplitude projects you can access. [Google's MCP reference](https://geminicli.com/docs/tools/mcp-server/) distinguishes configuration from authentication. Keep the app's client configuration in `.env.local`; MCP uses your account's permissions. Review requested access before continuing.

## 2. Give the agent a precise implementation prompt

In the interactive Gemini session, paste the prompt below. I review the proposed files and SDK changes before accepting edits. Run the app checks separately from the agent's explanation.

Implementation prompt

```text
Work in this repository and first inspect its app entry point, existing Amplitude initialization, consent flow, authentication, package manager and existing GEMINI.md instructions. Use current Amplitude docs and report the source URLs. Before changing code, describe the files you will touch and any conflicting existing setup.

Read the complete reference app at https://github.com/amplitude/ampli-examples/tree/1c8efa7b11d3d91033198e8ba3ead0579170e5e7/browser/coding-tools/feature-flags. Implement that standalone Vite report example, or merge the same behavior into the existing browser app. If you cannot read the reference, ask me to paste its files before implementing it. For the isolated development demo only: Use @amplitude/unified 1.1.43 with one initAll call and top-level serverZone. Read both a project API key and a client deployment key from Vite configuration; reject a blank deployment before initializing. Request sampleRate 0 and skip engagement. Set synthetic user build-web-flags-demo before fetch. Configure automaticExposureTracking false and retryFetchOnFailure false. Fetch report-layout once, accept only control and expanded, and display Compact fallback for empty, unknown or failed results. Control renders Compact; expanded shows daily totals. Call exposure(report-layout) once after the assigned DOM has had a paint opportunity. Do not expose the fallback. Keep conversions disabled while assignment is pending. Track Report Exported with report_period and layout_source only on export.

In an existing app, use its existing analytics client, confirmed user identity, consent lifecycle, and enabled products. Do not copy the demo synthetic IDs or its sample/skip choices into that client. If the existing installation is not Unified, propose a documented compatible integration before changing initialization; never add a parallel client. Preserve routes, authentication, replay privacy, consent and other product setup. Do not replace whole shared files or reset identity while authentication is anonymous or loading. Read only public client configuration through VITE_ variables; never use server or admin credentials. Keep .env.local ignored. Preserve the lockfile and frozen-install workflow. Report what was built, what was tested locally, and which service-side outcomes remain unverified. Stop the affected claim if current docs disagree; record both sources rather than guessing.
```

**MCP.** Ask the connected agent to inspect the project's existing event names before adding `Report Exported`. If your taxonomy already defines this action, use its established name consistently in the app and product configuration.

## 3. Review the proposed changes

Inspect the diff before accepting the implementation. I check the initialization and privacy paths before the visible UI, because a working button doesn't prove the integration is configured correctly.

Terminal

```sh
git diff -- package.json pnpm-workspace.yaml index.html main.js .gitignore .gemini/settings.json GEMINI.md
git status --short
```

Open every new file listed by `git status` too; `git diff` omits untracked files.

Confirm the agent uses `initAll`, sets `serverZone` at the top level, initializes once per page load, and preserves the existing client if the app already has one. Check that `.env.local` is ignored. Vite embeds `VITE_` values in the browser bundle; these values may contain client ingestion or client deployment keys, never server, admin, or secret credentials.

The complete example uses a fixed synthetic identity. In an existing app, connect your confirmed application identity and consent lifecycle. Don't overwrite your login handler, navigation, or another product's instrumentation with this demo.

## 4. Check the complete reference implementation

These are complete files for the new standalone example. Compare them with the agent's result. In an existing app, merge the relevant imports, handlers, configuration, and DOM targets without replacing unrelated files.

The [Unified SDK installation guide](https://amplitude.com/docs/sdks/analytics/browser/browser-unified-sdk/get-started) supplies the SDK setup. This example pins the version documented in the [1.1.43 API reference](https://amplitude.com/docs/sdks/analytics/browser/browser-unified-sdk/reference). Its initialization returns a plain promise, so the code awaits `initAll` directly.

package.json

```json
{
  "name": "amplitude-build-feature-flags",
  "private": true,
  "version": "1.0.0",
  "type": "module",
  "packageManager": "pnpm@10.33.2+sha512.a90faf6feeab71ad6c6e57f94e0fe1a12f5dcc22cd754db40ae9593eb6a3e0b6b12e3540218bb37ae083404b1f2ce6db2a4121e979829b4aff94b99f49da1cf8",
  "engines": {
    "node": ">=22.12.0",
    "pnpm": ">=10.21.0"
  },
  "scripts": {
    "dev": "vite --host 127.0.0.1",
    "build": "vite build",
    "preview": "vite preview --host 127.0.0.1"
  },
  "dependencies": {
    "@amplitude/unified": "1.1.43"
  },
  "devDependencies": {
    "vite": "8.3.4"
  }
```

pnpm-workspace.yaml

```yaml
minimumReleaseAge: 10080
trustPolicy: no-downgrade
```

.gitignore

```text
node_modules/
dist/
.env.local
.env.qa
```

.env.example

```text
VITE_AMPLITUDE_API_KEY=
VITE_AMPLITUDE_SERVER_ZONE=US
VITE_AMPLITUDE_DEPLOYMENT_KEY=
```

index.html

```html
<!doctype html>
<html lang="en">
  <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Usage report demo</title></head>
  <body>
    <main>
      <h1>Usage report</h1>
      <p>84 actions in the past 7 days.</p>
      <button id="start">Start demo</button>
      <button id="export-report">Export report</button>
      <p>Local exports: <span id="exports">0</span></p>
      <p>Layout: <span id="layout">Compact</span></p>
    <p id="detail" hidden>Daily totals: 12 actions per day.</p>
      <p id="status" role="status">Demo stopped. Use synthetic data only.</p>
    </main>
    <script type="module" src="/main.js"></script>
  </body>
</html>
```

main.js

```javascript
import * as amplitude from "@amplitude/unified";
const deploymentKey = (import.meta.env.VITE_AMPLITUDE_DEPLOYMENT_KEY || "").trim();
let layoutSource = "fallback";

const apiKey = (import.meta.env.VITE_AMPLITUDE_API_KEY || "").trim();
const zone = (import.meta.env.VITE_AMPLITUDE_SERVER_ZONE || "US").trim();
const status = document.querySelector("#status");
const startButton = document.querySelector("#start");
const exportButton = document.querySelector("#export-report");
let started = false;
let starting;
let exports = 0;

exportButton.addEventListener("click", () => {
  exports += 1;
  document.querySelector("#exports").textContent = String(exports);
  if (started) amplitude.track("Report Exported", { report_period: "7d", layout_source: layoutSource });
});

startButton.addEventListener("click", () => {
  if (starting) return;
  if (!apiKey || !["US", "EU"].includes(zone) || !deploymentKey) {
    status.textContent = "Add the development configuration and restart Vite. Local export still works.";
    return;
  }
  startButton.disabled = true;
  starting = start().catch(() => {
    status.textContent = "Setup failed. Check configuration and connectivity, then reload.";
  });

async function start() {
  await amplitude.initAll(apiKey, {
    serverZone: zone,
    analytics: { autocapture: false },
    sessionReplay: { sampleRate: 0 },
    experiment: { deploymentKey, automaticExposureTracking: false, retryFetchOnFailure: false, fetchTimeoutMillis: 5000 },
    engagement: { skip: true },
  });
  amplitude.setUserId("build-web-flags-demo");
  started = true;
  const client = amplitude.experiment();
  if (!client) throw new Error("Experiment client unavailable");
  // Do not log conversions under a fallback while assignment is still pending.
  exportButton.disabled = true;
  try {
    await client.fetch();
    const value = client.variant("report-layout").value;
    if (value === "control" || value === "expanded") {
      layoutSource = "assigned";
      document.querySelector("#layout").textContent = value === "expanded" ? "Expanded" : "Compact";
      document.querySelector("#detail").hidden = value !== "expanded";
      await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
      client.exposure("report-layout");
      status.textContent = `Assigned ${value}; device ${amplitude.getDeviceId()}.`;
    } else {
      status.textContent = "No usable assignment. Compact fallback; no assigned exposure.";
    }
  } catch {
    status.textContent = "Fetch failed. Compact fallback; no assigned exposure.";
  } finally {
    exportButton.disabled = false;
  }
```

Install from the checked-in manifest. Commit the generated lockfile, and use a frozen install in repeatable builds.

Terminal

```sh
corepack enable
corepack prepare pnpm@10.33.2 --activate
pnpm install
cp .env.example .env.local
```

Fill in `.env.local` with the development configuration. The client key is public and limited to ingestion, but keeping it in local configuration avoids tying shared source to one project. See [Vite's environment-variable reference](https://vite.dev/guide/env-and-mode) for rebuild and browser-exposure behavior.

## 5. Configure the development project

Create a feature flag in the development project with key `report-layout`. Use remote evaluation, add the values `control` and `expanded`, and attach the client deployment whose key you configured. Keep the general rollout at zero while testing; put only your intended synthetic testers in test targeting and save the configuration.

Implementation prompt

```text
Flag key: report-layout
Values: control, expanded
Synthetic user: build-web-flags-demo
control display: Compact
expanded display: Expanded, with daily totals
Fallback display: Compact, layout_source=fallback
Conversion: Report Exported
```

Test the two values separately in your development targeting. A fetch returning HTTP 200 can still contain no assignment for this flag. The app exposes only a recognized assigned value, and it records `layout_source` so fallback exports can be distinguished from assigned exports.

For an experiment, create an experiment or convert the flag, configure `[Experiment] Exposure` as its exposure event, and save `Report Exported` as the primary conversion metric. Review the hypothesis and targeting before selecting **Start Experiment**. A flag assignment alone doesn't mean an experiment has started or produced a statistically reliable result.

The sample requests a zero replay sample rate. Check the development project's replay capture settings too. Preserve any existing replay consent and privacy configuration when merging.

## 6. Run the report

Start Vite and open the local URL it prints. Export once before starting the SDK to confirm the report remains usable when analytics is unconfigured. Then tap Start demo once and perform the product check below.

Terminal

```sh
pnpm dev
```

After the development check, stop the server and verify the production build. Restart Vite after changing environment configuration.

Terminal

```sh
pnpm install --frozen-lockfile
pnpm build
```

## Verify it worked

1. Leave either client key blank. Export locally and tap Start demo. Confirm the configuration warning, Compact fallback, and no SDK initialization. A missing deployment key must not silently select another deployment.
2. With valid development keys, target `build-web-flags-demo` to `control`. Start once and confirm the report shows Compact with an Assigned status. Export once.
3. In a separate page load, target the same synthetic tester to `expanded`. Confirm the extra daily totals appear before the assigned exposure and export. Keep the general audience unchanged.
4. Test an empty or unavailable assignment. Confirm Compact fallback, no assigned exposure, and `layout_source: fallback` on any export tracked after initialization.
5. In User Lookup, check the processed `[Experiment] Exposure` and `Report Exported` events for the same user, device, and test time. The integrated SDK sends the exposure; don't add a second custom exposure event.
6. Open the experiment and confirm its saved exposure definition, primary conversion metric, deployment, and started state. Verify that processed test activity belongs to that setup. Do not infer a winner from a passing integration test.

## Troubleshooting

**Start demo asks for configuration.** Fill in `.env.local` and restart Vite. The guard deliberately leaves the SDK stopped when the required client configuration is missing or the region isn't US or EU.

**The report stays Compact.** Check whether the status says Assigned control or fallback. Verify the exact flag key, values, deployment association, tester inclusion, saved state, and region.

**A missing deployment still returns flags in another app.** This example rejects an empty client deployment key. Without that guard, the Unified integration can fall back to the project API key's deployment.

**An exposure arrives before the assigned report appears.** Keep automatic exposure disabled and the explicit exposure call after rendering. Remove automatic reads or duplicate exposure calls added by the agent.

**The fetch fails.** Keep the usable Compact fallback. Check connectivity and targeting, then reload for a new attempt. The demo disables background fetch retries and doesn't retry initialization within the same page load.

**The agent added a second SDK or broke an existing route.** Review the repository's original entry point and merge into its existing client. Keep one Amplitude initialization for this app; don't combine this example with a second script loader or independently initialized browser package.

## FAQ

### Which Amplitude key goes in the browser app?

The browser app uses a public project API key and a client deployment key. An MCP connection authenticates separately. Keep server deployment keys, admin tokens, and secret keys out of browser configuration and committed source.

### Can I use this in an existing React or Next.js app?

Yes. Have the agent merge the behavior into the existing browser entry point and client. Preserve authentication, consent, and routing. The standalone reference uses Vite; a server-rendered app needs its framework's browser-only lifecycle rather than a direct copy of the Vite entry file.

### Does a successful build prove the product works?

No. A build checks that the browser app can be bundled. The Verify section separately checks the rendered experience, processed activity, and the product result. Record which checks you performed before broadening the audience.

### Will the agent make the same changes every time?

Treat its output as a change to review. Keep the explicit prompt, pinned reference, and repository rules, then compare its diff with the required behavior. An agent can propose a different implementation without establishing that its SDK calls or product settings are correct.

## Full code reference

Step 4 contains every application and configuration file. [Download the complete example](https://github.com/amplitude/ampli-examples/tree/1c8efa7b11d3d91033198e8ba3ead0579170e5e7/browser/coding-tools/feature-flags) with its lockfile and setup README. Keep `.env.local` ignored and commit the package lock when you customize the example.

Append these constraints to the repository's [GEMINI.md context file](https://geminicli.com/docs/cli/gemini-md/), preserving its existing instructions. Inside Gemini, use `/memory reload` after editing it, then `/memory show` to inspect the loaded context. Keep credentials out of the context file.

GEMINI.md

```markdown
## Amplitude report instrumentation

Inspect existing Amplitude initialization, consent, authentication and project rules first.
Use the existing client and current official SDK documentation. Initialize once.
In a Unified integration, keep serverZone at the options' top level.
Preserve enabled products, identity, routes, private-data controls and the lockfile.
Synthetic users, sampling and skip options in a standalone demo are not host-app defaults.
Do not add a parallel client when merging into an existing integration.
Keep private and server credentials out of browser configuration and source.
Keep report-layout assignment and the rendered report in sync.
Fetch after the confirmed identity is set, then handle empty, unknown and failed results.
Use one exposure method for this path; expose only the recognized assigned layout after render.
Keep fallback exports distinguishable and conversions disabled while assignment is pending.
Do not start an experiment or change audience targeting as part of a code edit.
Record what was built, locally tested and observed in the live service separately.
If sources conflict, record both and pause the affected claim before implementation.
```

## What this unlocks

[Feature flags](https://amplitude.com/feature-management) assigns the report layout. [Experimentation](https://amplitude.com/amplitude-experiment) connects `[Experiment] Exposure` to the primary conversion `Report Exported` for the same identity. Keep the event name and report-period property consistent as you extend the app.

## Next steps

[FoundationGemini CLI analyticsReview the related analytics walkthrough as optional context.](https://amplitude.com/build/gemini-cli-product-analytics)[ReferenceBrowser SDK configurationCheck options before adding more product behavior.](https://amplitude.com/docs/sdks/analytics/browser/browser-unified-sdk/reference)[WorkflowDebug your eventsConfirm identity and event properties in your development project.](https://amplitude.com/docs/analytics/debug-analytics)

Reference docs

[Unified SDK setup →](https://amplitude.com/docs/sdks/analytics/browser/browser-unified-sdk/get-started)[Unified SDK API reference →](https://amplitude.com/docs/sdks/analytics/browser/browser-unified-sdk/reference)[Product reference →](https://amplitude.com/docs/sdks/experiment-sdks/experiment-javascript)[Amplitude MCP →](https://amplitude.com/docs/amplitude-ai/amplitude-mcp)[Gemini CLI context files →](https://geminicli.com/docs/cli/gemini-md/)[Vite setup →](https://vite.dev/guide/)

Amplitude is the leading [all-in-one analytics platform](https://amplitude.com/) for building better products. Product and engineering teams use Amplitude to connect [product analytics](https://amplitude.com/amplitude-analytics) and [web analytics](https://amplitude.com/web-analytics) with [session replay](https://amplitude.com/session-replay), [heatmaps](https://amplitude.com/heatmaps), [experimentation](https://amplitude.com/amplitude-experiment), [feature flags](https://amplitude.com/feature-management), and [guides and surveys](https://amplitude.com/guides-and-surveys) in one unified workflow, with [AI agents](https://amplitude.com/ai-agents) working across all of it. Learn how Amplitude is building [self-improving products](https://amplitude.com/self-improving-products) with [Wave](https://amplitude.com/wave), helping teams turn product data into PRs.

For agents

[View raw .md](/build/gemini-cli-feature-flags.md)[llms.txt](/llms.txt)

## Build the report, then check the result

Connect a development project and verify the report action before you roll out the integration.

[Create a free account](https://app.amplitude.com/signup)[Browse all Build tutorials](https://amplitude.com/build)
