All posts
7 min readBy BetterCodePush

Remote Config for React Native: change your app without shipping anything

Copy, limits, kill switches and feature flags, changed from the console and picked up by devices on their next fetch. How Remote Config works in BetterCodePush 1.0, and when to use it instead of an OTA update.

Remote ConfigFeature FlagsReact NativeExpo

A React Native app changes in three ways. Native code changes need a store release. JavaScript changes can go out as an OTA update. And a lot of what product teams ask for is neither: a different welcome message, a lower page size, a banner for a weekend sale, a switch that turns off a feature misbehaving in one market. Those are values, and shipping a new bundle to change a value is like redeploying a server to edit a row in a database.

Remote Config, new in BetterCodePush 1.0, is for those values. You define parameters and the conditions that pick their values. Each device fetches the values that apply to it, and your app reads them with typed getters and compiled-in defaults.

Setup in two steps

Remote Config is a plugin for the BetterCodePush SDK you already have. It is plain JavaScript, with no native module, no config files to add to your iOS and Android projects, and nothing to rebuild. It works the same in bare React Native and Expo apps.

npm install @bettercodepush/remote-config
import { BetterCodePush } from "@bettercodepush/react-native";
import { remoteConfig } from "@bettercodepush/remote-config";

export const betterCodePush = BetterCodePush.init({
  plugins: [
    remoteConfig({
      defaults: { welcome_message: "Welcome", page_size: 20, new_checkout: false },
    }),
  ],
});

// fetch in the background; on failure the app keeps its current values
betterCodePush.remoteConfig.fetchAndActivate().catch(() => {});

export default betterCodePush.wrap({ updateStrategy: "appVersion" })(App);

Then read values anywhere, synchronously:

const { remoteConfig } = betterCodePush;

remoteConfig.getString("welcome_message"); // "Welcome" until a fetch says otherwise
remoteConfig.getNumber("page_size");
remoteConfig.getBoolean("new_checkout");

Every key you declare in defaults always has a value, so a read never returns undefined on a first launch, offline or on a device that has never fetched. Activated values are stored on the device and used from the very next launch, before any network request.

If you want components to re-render when new values are activated, the plugin is built to plug straight into useSyncExternalStore:

import { useSyncExternalStore } from "react";

export function useRemoteConfig() {
  const { remoteConfig } = betterCodePush;
  return useSyncExternalStore(remoteConfig.subscribe, remoteConfig.getAll);
}

// in a component
const config = useRemoteConfig();
const showNewCheckout = config.new_checkout.asBoolean();

Conditions: who gets which value

Values are published as a template: a list of parameters and a list of named conditions. A condition is a set of rules that must all match. For each parameter, a device gets the value of the first condition that matches it, or the parameter's default.

Rules can match on:

  • platform, iOS or Android;
  • channel, for example beta or production;
  • app version, as a semver range such as >=2.4.0;
  • a percentage of installs, stable per device;
  • named cohorts, for example your internal testers;
  • a native build fingerprint;
  • a time window, for a sale or a launch that starts at a set time.

Here is a template for a common case: a weekend sale banner, and a new checkout turned on for 10% of installs but never for app versions that do not contain the new checkout code.

{
  "conditions": [
    {
      "name": "Sale weekend",
      "rules": [{ "type": "dateTime", "from": "2026-11-27T00:00:00-05:00", "to": "2026-12-01T00:00:00-05:00" }]
    },
    { "name": "Before 2.4", "rules": [{ "type": "appVersion", "range": "<2.4.0" }] },
    {
      "name": "10 percent",
      "rules": [{ "type": "percent", "seed": "new-checkout", "from": 0, "to": 10 }]
    }
  ],
  "parameters": {
    "sale_banner": {
      "valueType": "STRING",
      "defaultValue": { "value": "" },
      "conditionalValues": { "Sale weekend": { "value": "30% off everything until Monday" } }
    },
    "new_checkout": {
      "valueType": "BOOLEAN",
      "defaultValue": { "value": "false" },
      "conditionalValues": {
        "Before 2.4": { "value": "false" },
        "10 percent": { "value": "true" }
      }
    }
  }
}

Because "Before 2.4" comes first, an install on 2.3 never gets the new checkout, even if it falls inside the 10%. Ordering conditions from most specific to least specific keeps templates like this easy to reason about.

The same cohorts as your OTA rollouts

This is the part a standalone config service cannot do. BetterCodePush already splits your installs into stable cohorts to run staged rollouts, and a percentage rule in Remote Config uses those same cohorts. Seed a 0 to 10% rule with a release's ID, or pick the release in the console editor, and the rule matches exactly the devices that a 10% rollout of that release reaches.

That lets you ship code and configuration together. Deploy a bundle with a new feature to 10% of devices, turn the feature on for the same 10% with a config value, and if something looks wrong in release health, switch the value off without touching the release. Widen both together when the numbers look right.

Publishing, previewing and rolling back

Templates are edited in the console, where you can add parameters and conditions, publish with a description, and browse every version you have published. Teams that prefer configuration in Git can do the same from the CLI:

npx bettercodepush remote-config show --platform ios --json > remote-config.ios.json
# edit the file, then check what a beta device on 2.4.0 would get
npx bettercodepush remote-config preview --file remote-config.ios.json --platform ios \
  --channel beta --app-version 2.4.0
npx bettercodepush remote-config publish remote-config.ios.json --platform ios \
  --description "Sale weekend banner"
# something wrong? republish an earlier version
npx bettercodepush remote-config versions --platform ios
npx bettercodepush remote-config rollback 3 --platform ios

Every publish is a new version, and a rollback publishes a copy of an older version, so history is never rewritten. A publish is refused if someone else published after you exported the template, so two people editing at once cannot silently overwrite each other's changes.

What Remote Config is not

Three design choices are worth knowing before you rely on it.

It is not real-time. By default a device fetches at most once every 12 hours, and new values apply when your app calls activate(). That keeps requests rare and suits values that change over hours or days. If you use a value as a kill switch, plan around that delay: it limits damage over the following hours, while disabling an OTA release takes effect on the next update check.

It is not secret. Every device of your app can read the values that match it. Never put API keys, tokens or personal data in a template.

Defaults come first. A first launch without a network uses only the defaults compiled into your app, so every key you read should have a sensible default.

Remote Config or an OTA update?

If the change is data, use Remote Config. If it is logic, ship an OTA update. A good test: could the code that reads the value handle every value you might set? Then it is config. If you find yourself encoding behaviour in JSON for a generic engine to interpret, you are building server-driven UI, which is sometimes the right call but deserves its own decision. Our post on feature flags vs OTA updates goes deeper into where each one belongs.

Coming from Firebase Remote Config

Firebase Remote Config is the default many React Native teams reach for, and if you already use Firebase Analytics and A/B Testing it ties into both. If Firebase is in your app only for config, it is a native SDK, platform config files and a store release to set up. Remote Config in BetterCodePush uses the SDK that already ships your updates. The concepts will feel familiar: parameters, conditions, defaults, and separate fetch and activate steps.

Plans

Remote Config is included in the Starter, Pro and Enterprise plans, with no charge per fetch: a device that fetches config is already one of your monthly active devices. Each platform is its own app with its own template, so iOS and Android can differ when they need to. See pricing for the plans.

On BetterCodePush 1.0 already? Install the plugin and publish your first template from the console. Coming from an earlier version, start with the 1.0 upgrade guide.

Ship your first update

Create an organization and deploy an over-the-air update in minutes. No billing setup required.

Start with BetterCodePush