Skip to main content

October 15, 2026

A kill switch must default to on, at both ends

I flip Firebase kill switches when OAuth breaks; full deploy wiped my console off, and static reads hid sign-in buttons until I fixed template defaults.

Sander Korf4 min read
firebaseremote-configfeature-flagsreact-nativeci

I work on a creator marketplace app where new creators land on a sign-in screen and pick Apple, TikTok, Instagram, or YouTube to register or link an account. Each provider has a boolean kill switch in Firebase Remote Config (keys like social_tiktok_enabled, default true). When TikTok OAuth breaks at 2am I flip that parameter to false in the Firebase console and the React Native build hides that button without waiting for App Store review. The keys and their defaults live in a JSON template in git; CI pushes that template toward Firebase on main merges and release tags.

A Remote Config template is the versioned JSON for parameters, defaults, conditions, and groups. A console override is what ops sets in the Firebase UI until the next sync. ETag / If-Match let a publish fail if the live template changed meanwhile. Value source is static, default, or remote on getValue().getSource(). A kill switch means the provider is enabled; off hides sign-in and connect buttons.

TikTok's token endpoint failed at 2am. I set social_tiktok_enabled to false and support calmed down. Next morning CI on an unrelated merge ran firebase deploy --only remoteconfig, and TikTok showed up again while OAuth was still dead. I had treated git sync as harmless. For kill switches it is not.

Full deploy overwrites console overrides

Full publish replaces live parameters with the repo file, so incident overrides disappear. Staging syncs when the template changes on main; production on release tags. Neither should undo a deliberate false.

Add-only sync: GET live, keep ETag, add only missing keys (top level and groups) from repo without touching existing values, conditions, or descriptions. PUT with If-Match so a concurrent console edit fails instead of being lost. Strip read-only version fields; keep version.description. --dry-run for CI logs.

const live = await getRemoteConfigTemplate()
const etag = live.etag
const merged = structuredClone(live)
for (const [key, spec] of Object.entries(repo.parameters)) {
  if (merged.parameters[key]) continue
  merged.parameters[key] = spec
}
await putRemoteConfigTemplate(stripReadOnly(merged), { ifMatch: etag })

Deleting a parameter to disable a provider fails for the same reason: the next add-only sync sees the key in git and reinserts it as default true. Disable by setting the value to false, in console or in the template.

Uncheck First launch / offline, turn off social_tiktok_enabled, pick Full deploy or Add-only sync, then click CI sync from repo template. Full deploy snaps TikTok back on; add-only leaves your false in place.

Remote Config kill switch and CI sync

Fake Firebase console, CI sync mode, and a phone sign-in screen. Compare naive asBoolean on static values vs template-aware reads.

Firebase console (live override)

sync mode: add-only

Sign in

source: static · read: static-aware

Visible provider buttons: 4 of 4

Turn TikTok off in the console panel, then hit CI sync. Try full deploy vs add-only. Toggle first launch to see naive reads hide every button.

Static source makes "enabled" read as false

On first launch or offline before fetch, getSource() is static. Leave First launch / offline checked and tap Naive asBoolean(): every sign-in button hides. Static-aware read uses the same template defaults CI syncs. Firebase Remote Config docs say static values are not remote yet; asBoolean() on them returns false, so an enabled switch fails closed without that guard.

In-app defaults must come from the same JSON template CI syncs, then:

function flag(key: string) {
  const d = DEFAULTS[key] === 'true'
  try {
    const v = getValue(rc, key)
    return v.getSource() === 'static' ? d : v.asBoolean()
  } catch {
    return d
  }
}

asBoolean() also treats 1, yes, and on as true once remote values arrive; the bug is specifically the static path before fetch.

Guard rails when a provider is off

Linked accounts stay visible and disconnectable when their flag is off. If every linking provider is off, onboarding still continues with temporarily unavailable copy.

Same idea for any flag store with config-as-code plus live console edits, not single-writer config. Git owns default-on; the console owns temporary off; the client must not treat pre-fetch as off.


Happy coding! Sander