---
title: "Feature Flags in a Capacitor App with Remote Config"
description: Add feature flags to a Capacitor app with Firebase Remote Config — fetch and activate, staged rollouts, real-time updates, and combining with live updates.
date:
  created: 2026-09-06
  updated: 2026-09-06
authors:
  - djabif
categories:
  - Capacitor
  - Firebase
  - Guides
  - SDKs
links:
  - Capacitor Firebase Remote Config: sdks/capacitor/firebase/remote-config.md
faq: true
---

# Feature Flags in a Capacitor App with Remote Config

Shipping a feature behind a flag you can flip from a dashboard beats shipping it and hoping. [Firebase Remote Config](https://firebase.google.com/docs/remote-config){:target="_blank"} is a cloud key-value store your app reads at runtime, so you can turn features on for 10% of users, run a sale for a weekend, or kill a broken screen, all without publishing an update. The [Capacitor Firebase Remote Config plugin](../../sdks/capacitor/firebase/remote-config.md) brings it to Capacitor apps through the native Android and iOS SDKs.

This guide covers feature flags end to end: defining parameters in the console, installing and configuring the plugin, fetching and activating values, reading them as flags, reacting to changes in real time, and tuning the fetch throttle.

<!-- more -->

<div class="capawesome-z29o10a">
  <a href="/" target="_blank">
    <img alt="Build and deploy your Capacitor app with Capawesome Cloud" src="https://capawesome.io/assets/banners/cloud-build-and-deploy-capacitor-apps.png?t=1" />
  </a>
</div>

## Key Takeaways

- Remote Config is a cloud key-value store for feature flags, staged rollouts, and remote toggles. It changes *parameters you defined*, not your app's code.
- Fetched values aren't live until you activate them. [`fetchAndActivate()`](../../sdks/capacitor/firebase/remote-config.md#fetchandactivate) does both; `fetchConfig()` + `activate()` splits them.
- Read values with [`getBoolean(...)`](../../sdks/capacitor/firebase/remote-config.md#getboolean), `getNumber(...)`, and `getString(...)`.
- The default minimum fetch interval in production is 12 hours, which is why config changes often look like they never arrive. Lower it with `setSettings(...)` during development.
- On Android and iOS, `addConfigUpdateListener(...)` pushes changes in real time so flags can flip without a restart.
- Set default values in the console so the getters return something sensible before the first fetch.

## How to Add Feature Flags to a Capacitor App with Remote Config

Adding feature flags with Firebase Remote Config in a Capacitor app takes four steps:

1. **Define parameters** (your flags) in the Firebase console with default values.
2. **Install** `@capacitor-firebase/remote-config` and sync the native projects.
3. **Fetch and activate** the config at startup with `fetchAndActivate()`.
4. **Read the flags** with `getBoolean()` / `getNumber()` / `getString()` and branch on them.

Everything below expands on those steps, then gets into real-time updates, the fetch throttle, best practices, and troubleshooting.

## What Is Firebase Remote Config?

[Firebase Remote Config](https://firebase.google.com/docs/remote-config){:target="_blank"} is a cloud-hosted key-value store for your app's configuration. You define parameters in the Firebase console, for example a boolean `is_sale`, a number `max_upload_mb`, or a string `welcome_message`. Parameters can be scoped by conditions (audience, app version, region, percentage rollout), and your app fetches and reads them at runtime. The [Capacitor Firebase Remote Config plugin](../../sdks/capacitor/firebase/remote-config.md) exposes the native Android and iOS Remote Config SDKs, plus the Firebase JS SDK on web, behind one shared TypeScript API.

## Why Use Remote Config in a Capacitor App?

Typical jobs Remote Config takes on in a Capacitor app:

- **Feature flags.** Roll out new features gradually by toggling boolean parameters remotely.
- **Promotions.** Turn a sale or promotion on and off remotely with a parameter like `is_sale`.
- **Maintenance announcements.** Warn users about upcoming maintenance without shipping an update.
- **Real-time updates.** React to configuration changes as they happen with the config update listener.
- **Sensible defaults.** Provide in-app default values so the app behaves predictably before its first fetch.

## Remote Config vs. Live Updates: Which Do You Need?

Remote Config changes values your app already knows how to read: a flag, a number, a string. It can hide a feature you already shipped, but it can't add code that isn't in the app. Live updates (over-the-air updates) replace your app's web bundle, so they *can* ship new code and UI without an app store release.

Reach for Remote Config to flip behavior you've already built. Reach for a live update (via [Capawesome Cloud](https://capawesome.io/){:target="_blank"}) to ship the feature itself. They pair well: build a feature behind a Remote Config flag, deliver it with a live update, then enable it for a slice of users from the console.

## Before You Start

All you need to start is a Capacitor app with the `android` and/or `ios` platforms added and a Firebase project. The [Firebase console](https://console.firebase.google.com/){:target="_blank"} walks you through creating one. For audience- or user-property-based targeting, Remote Config relies on Google Analytics, so also add the [Capacitor Firebase Analytics plugin](./capacitor-firebase-analytics-guide.md) if you plan to target by audience.

## Step 1: Define Parameters in the Firebase Console

1. In the [Firebase console](https://console.firebase.google.com/){:target="_blank"}, open **Run → Remote Config** and click **Create configuration**.
2. Add a parameter for each flag or value, for example a boolean `is_sale`, and give it a **default value**. The default is what every client gets until a condition says otherwise, and what your getters fall back to before the first fetch.
3. To roll out gradually, add a **condition** on the parameter: a percentage of users, an app version range, a specific audience, and so on. This is how you flip a feature on for 10% of users without touching the app.
4. Click **Publish changes**. Nothing reaches your app until you publish.

## Step 2: Install the Plugin

Install the plugin and the `firebase` package (used on the web layer), then sync the native projects:

```bash
npm install @capacitor-firebase/remote-config firebase
npx cap sync
```

## Step 3: Add Firebase to Your Native Apps

Add the native config files Firebase generates ([full reference](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md){:target="_blank"}): `google-services.json` in `android/app/`, and `GoogleService-Info.plist` added to the Xcode project in `ios/App/App/`. For the web platform, initialize the Firebase JS SDK with your web app's config.

For an iOS project on Swift Package Manager, the plugin also needs the `symlink` package option in `capacitor.config.ts` (available since Capacitor CLI 8.4.0) to avoid a SwiftPM package identity collision:

```json
{
  "experimental": {
    "ios": {
      "spm": {
        "packageOptions": {
          "@capacitor-firebase/remote-config": { "symlink": true }
        }
      }
    }
  }
}
```

## Fetching and Activating the Config

Remote Config splits the work in two: fetching downloads the latest values, and activating makes those fetched values available to the getters. Until you activate, your app keeps reading the previously active values. [`fetchAndActivate()`](../../sdks/capacitor/firebase/remote-config.md#fetchandactivate) does both in one call, which is what you want at startup:

```typescript
import { FirebaseRemoteConfig } from '@capacitor-firebase/remote-config';

const loadRemoteConfig = async () => {
  await FirebaseRemoteConfig.fetchAndActivate();
};
```

If you'd rather control the timing, split them with [`fetchConfig(...)`](../../sdks/capacitor/firebase/remote-config.md#fetchconfig) and [`activate()`](../../sdks/capacitor/firebase/remote-config.md#activate). That lets you fetch in the background and activate at a safe moment, like the next screen transition, so values don't change mid-screen:

```typescript
import { FirebaseRemoteConfig } from '@capacitor-firebase/remote-config';

const fetchInBackground = async () => {
  await FirebaseRemoteConfig.fetchConfig({ minimumFetchIntervalInSeconds: 3600 });
};

const activateWhenSafe = async () => {
  await FirebaseRemoteConfig.activate();
};
```

## Reading Flags and Values

Once the config is active, read a parameter by key as a boolean, number, or string:

```typescript
import { FirebaseRemoteConfig } from '@capacitor-firebase/remote-config';

const isSaleActive = async () => {
  const { value } = await FirebaseRemoteConfig.getBoolean({ key: 'is_sale' });
  return value;
};

const maxUploadMb = async () => {
  const { value } = await FirebaseRemoteConfig.getNumber({ key: 'max_upload_mb' });
  return value;
};

const welcomeMessage = async () => {
  const { value } = await FirebaseRemoteConfig.getString({ key: 'welcome_message' });
  return value;
};
```

A feature flag is a boolean parameter you branch on:

```typescript
if (await isSaleActive()) {
  // show the sale banner
}
```

## Reacting to Flag Changes in Real Time

On Android and iOS, you don't have to wait for the next fetch to notice a change. `addConfigUpdateListener(...)` fires when parameter values update on the server, so a flag can flip while the app is open. The pattern differs by framework, and Angular needs `NgZone` because the callback fires outside its change-detection zone:

=== "Angular"

    ```typescript
    import { Injectable, NgZone, signal } from '@angular/core';
    import { FirebaseRemoteConfig } from '@capacitor-firebase/remote-config';

    @Injectable({ providedIn: 'root' })
    export class FeatureFlagsService {
      readonly saleActive = signal(false);

      constructor(private readonly zone: NgZone) {
        FirebaseRemoteConfig.addConfigUpdateListener(async (_event, error) => {
          if (error) return;
          await FirebaseRemoteConfig.activate();
          const { value } = await FirebaseRemoteConfig.getBoolean({ key: 'is_sale' });
          this.zone.run(() => this.saleActive.set(value));
        });
      }
    }
    ```

=== "React"

    ```tsx
    import { useEffect, useState } from 'react';
    import { FirebaseRemoteConfig } from '@capacitor-firebase/remote-config';

    export const useFeatureFlag = (key: string) => {
      const [enabled, setEnabled] = useState(false);
      useEffect(() => {
        const id = FirebaseRemoteConfig.addConfigUpdateListener(async (_event, error) => {
          if (error) return;
          await FirebaseRemoteConfig.activate();
          const { value } = await FirebaseRemoteConfig.getBoolean({ key });
          setEnabled(value);
        });
        return () => {
          id.then(callbackId => FirebaseRemoteConfig.removeConfigUpdateListener({ id: callbackId }));
        };
      }, [key]);
      return enabled;
    };
    ```

=== "Vue"

    ```typescript
    import { onMounted, onUnmounted, ref } from 'vue';
    import { FirebaseRemoteConfig } from '@capacitor-firebase/remote-config';

    export const useFeatureFlag = (key: string) => {
      const enabled = ref(false);
      let callbackId: string | undefined;
      onMounted(async () => {
        callbackId = await FirebaseRemoteConfig.addConfigUpdateListener(async (_event, error) => {
          if (error) return;
          await FirebaseRemoteConfig.activate();
          const { value } = await FirebaseRemoteConfig.getBoolean({ key });
          enabled.value = value;
        });
      });
      onUnmounted(() => {
        if (callbackId) FirebaseRemoteConfig.removeConfigUpdateListener({ id: callbackId });
      });
      return enabled;
    };
    ```

The real-time listener is Android and iOS only. On the web, re-run `fetchAndActivate()` when you want the latest values.

## Tuning the Fetch Interval

Remote Config throttles fetches. By default the SDK won't fetch again within about 12 hours of the last successful fetch, and serves the cached values instead. That saves bandwidth and load on the service in production, but during development it looks like your config is "stuck." Lower the minimum fetch interval with [`setSettings(...)`](../../sdks/capacitor/firebase/remote-config.md#setsettings) while you're iterating:

```typescript
import { FirebaseRemoteConfig } from '@capacitor-firebase/remote-config';

const configureForDevelopment = async () => {
  await FirebaseRemoteConfig.setSettings({
    minimumFetchIntervalInSeconds: 0,
    fetchTimeoutInSeconds: 10,
  });
};
```

Set it back to a sensible production value (hours, not zero) before you ship, or you'll fetch far more often than you need to.

## Combining Live Updates and Feature Flags

Remote Config and live updates solve different halves of shipping a feature. Using them together decouples deploying the code from releasing the feature, a pattern sometimes called a dark launch:

1. **Build the feature behind a flag.** Wrap the new code in a Remote Config boolean like `new_checkout_enabled`, defaulting to `false`.
2. **Ship the code with a live update.** Deliver the new web bundle to every device over the air (for example with [Capawesome Cloud](https://capawesome.io/){:target="_blank"}). The code is now installed everywhere but dormant. Nobody sees it, because the flag is off.
3. **Turn it on gradually from the console.** Flip the flag to `true` for 10% of users with a condition, watch that segment in [Analytics](./capacitor-firebase-analytics-guide.md) and [Crashlytics](./capacitor-firebase-crashlytics-guide.md), then widen the rollout, or switch it off instantly if something breaks, with no new deployment.

Rolling back a bad release becomes a flag flip instead of an app store submission. The live update carries the code, the flag controls who gets it.

## Remote Config Best Practices

### Always Define Defaults in the Console

The getters need something to return before the first successful fetch and activate, typically on a fresh install with no network. Set a default value for every parameter so a first-run user gets a predictable experience instead of a `false`/`0`/empty string you didn't intend.

### Don't Store Secrets in Remote Config

Parameter values are delivered to the client and can be inspected. Use it for flags and tuning, never for API keys, credentials, or anything that grants access. That logic belongs in a [Cloud Function](./capacitor-firebase-cloud-functions-guide.md).

### Activate at a Predictable Moment

If you activate mid-screen, values can change under the user. Fetch in the background and activate at a natural boundary, like app launch or a screen transition, so the UI doesn't shift unexpectedly.

### Target With Analytics, Not Guesswork

Percentage rollouts and audience conditions rely on Google Analytics. With the [Capacitor Firebase Analytics plugin](./capacitor-firebase-analytics-guide.md) in place, you can flip a flag for a real audience segment instead of everyone at once.

## Common Errors and Troubleshooting

- **My config changes don't reach the app.** The fetch throttle is serving cached values, since the default minimum interval is ~12 hours. Lower it with `setSettings(...)` during development, and remember to **Publish changes** in the console.
- **`getBoolean(...)` returns the old value after a fetch.** You fetched but didn't activate. Use `fetchAndActivate()`, or call `activate()` after `fetchConfig(...)`.
- **Getters return `false` / `0` / empty on first run.** No value has been fetched yet and the parameter has no console default. Define defaults for every parameter.
- **The real-time listener never fires.** It's Android and iOS only. On the web, re-run `fetchAndActivate()` to pull the latest values.
- **Percentage rollouts and audiences don't apply.** Conditional targeting needs Google Analytics; add the [Capacitor Firebase Analytics plugin](./capacitor-firebase-analytics-guide.md).
- **App crashes on launch.** The `google-services.json` / `GoogleService-Info.plist` file is missing or misplaced (Step 3).

## FAQ

### How do I use Firebase Remote Config as a feature flag in a Capacitor app?

Define a boolean parameter (for example `is_sale`) in the Firebase console with a default value, call [`fetchAndActivate()`](../../sdks/capacitor/firebase/remote-config.md#fetchandactivate) at startup, then read it with [`getBoolean(...)`](../../sdks/capacitor/firebase/remote-config.md#getboolean) and branch on the result. Add a condition on the parameter to roll it out to a percentage of users or a specific audience.

### Why isn't my Remote Config updating?

Almost always the fetch throttle. The SDK won't fetch again within about 12 hours of the last successful fetch by default, so you keep getting cached values. Lower the minimum fetch interval with `setSettings(...)` while developing, confirm you clicked **Publish changes** in the console, and make sure you're activating after fetching.

### What's the difference between Remote Config and live updates?

Remote Config changes values your app already reads (flags, numbers, strings), but can't add code. Live updates (over-the-air updates, e.g. via [Capawesome Cloud](https://capawesome.io/){:target="_blank"}) replace your web bundle and can ship new code and UI. Use Remote Config to toggle behavior you've built; use a live update to ship the behavior.

### Can I change my app's UI or add features with Remote Config?

Only within what your app already knows how to render. You can hide or show a screen you shipped, or change a value it reads, but you can't introduce new code or UI that isn't in the installed bundle. That requires an app update or a live update.

## Deliver the Code Your Flags Gate

A feature flag can only reveal code that's already on the device, which is exactly what a live update puts there. [Capawesome Cloud](https://capawesome.io/){:target="_blank"} builds your iOS and Android apps in the cloud and ships new web-layer code over the air, so you can dark-launch a feature behind a Remote Config flag and flip it on from the console the same day, and automate App Store and Play Store submission when a native release is needed.

[Try Capawesome Cloud Free](https://capawesome.io){ .md-button .md-button--primary }

## Conclusion

Start with one boolean parameter and a `fetchAndActivate()` call at startup, and add conditions only once you can watch that flag change behavior on a device. When the change you want needs code that isn't in the installed app, that's a live update, not a Remote Config parameter.

If you want to go deeper from here:

- [Track App Events with Firebase Analytics in Capacitor](./capacitor-firebase-analytics-guide.md) (required for audience and percentage targeting of your flags).
- [Call Firebase Cloud Functions from a Capacitor App](./capacitor-firebase-cloud-functions-guide.md) (where secret-dependent logic belongs, instead of a Remote Config value).
- [Capacitor Firestore: Real-Time Data & Offline Sync](./capacitor-firebase-cloud-firestore-guide.md) (where the features you're flagging usually read their data).

If a flag won't flip or you're weighing a rollout strategy, ask in the [Capawesome Discord server](https://discord.gg/VCXxSVjefW){:target="_blank"}. The [Capawesome newsletter](https://capawesome.io/newsletter/){:target="_blank"} will land the next guide in your inbox.
