Firebase Remote Config limitations: documented limits and production realities
Every hard limit in the official docs, including 3,000 parameters, 2,000 conditions, 300 stored versions, and the 12-hour default fetch interval, plus the practical problems that only show up in production and what to do about each one.
Firebase Remote Config's limits are documented, but they are scattered across half a dozen doc pages: parameters here, templates there, fetch intervals somewhere else again. This page collects all of them in one place, verified against the official docs in June 2026, and pairs each one with what it actually means once your app is in production. Some limits you will never touch. A few of them will shape how you work.
The honest takeaway up front: most of the numbers are generous, and Remote Config is free for unlimited daily active users on both the Spark and Blaze plans. It is good at the job it was built for. The three limits that actually bite are the 12-hour default fetch interval, the missing environment model, and the 300-version history. Everything else in the table below is mostly trivia until you scale into it.
Every documented limit in one table
These come from the parameters and templates reference pages. The right column is the part the docs do not spell out.
| Limit | Value | What it means in practice |
|---|---|---|
| Parameters per project | 3,000 | A hard ceiling on how many keys you can define. Generous, but it is per project, and Firebase wants one project per environment, so it is really per environment. |
| Conditions per project | 2,000 | Conditions are targeting rules, not environments. You hit this only with heavy per-device targeting. |
| Parameter key length | 256 characters | Keys must start with an underscore or an English letter. Long namespaced keys can run into this. |
| Total size of all values | 1,000,000 characters | The combined length of every parameter value string in the project. Large JSON blobs eat into this shared budget fast. |
| Stored template versions | 300 per template type | Client and server templates are counted separately. The oldest is auto-deleted, and each version has a 90-day lifetime, so your audit trail is capped twice over. |
| Running experiments and rollouts | 24 (300 total) | Up to 24 experiments and rollouts can run at once, with 300 experiments total per project over the project lifetime. |
| Concurrent real-time connections | 20,000,000 | Beyond this, real-time clients fall back to polling on the fetch interval. Only an issue at very large scale. |
| Default minimum fetch interval | 12 hours | Without real-time listeners, a config change can take up to 12 hours to reach a client. This is the one that surprises people. |
The 12-hour fetch interval (why your change is not showing up)
This is the single limit most likely to cost you an afternoon. The default and recommended production value for minimumFetchIntervalMillis is 12 hours, on both mobile and web. The loading docs state it plainly: configs will not be fetched from the backend more than once in a 12-hour window, regardless of how many fetch calls you make.
// Default and recommended production value: 12 hours.
const remoteConfig = getRemoteConfig(app);
remoteConfig.settings.minimumFetchIntervalMillis = 43_200_000;
// Lower it during development, and you risk throttling in production.
remoteConfig.settings.minimumFetchIntervalMillis = 60_000; // 1 minuteSo you publish a change, open your app, and nothing happens. The fix looks obvious: lower the interval. That is where the second surprise lands. Fetch more often than the window allows and Remote Config throttles you. On the web you get a fetch-throttled error from fetchAndActivate(); on Android you get a FirebaseRemoteConfigFetchThrottledException. The get-started guide warns about this directly.
// Android
catch (FirebaseRemoteConfigFetchThrottledException e) {
// You called fetch() again inside the minimum fetch window.
// Remote Config refuses the call rather than hitting the backend.
}
// Web
// fetchAndActivate() rejects with error code "remoteconfig/fetch-throttled"The real fix is real-time Remote Config, which keeps an HTTP connection open and bypasses the fetch interval entirely. It works well, but it has its own constraints worth knowing before you commit:
- Apple needs SDK 10.7.0 or newer. See the real-time docs.
- Android needs SDK 21.3.0 or newer.
- Web only got it in September 2025. Real-time reached the web in Firebase JS SDK 12.3.0, per the JS release notes. Developers had asked for it since April 2024 in firebase-js-sdk discussion #8148, so web lagged mobile by about two and a half years.
- 20 million concurrent connections per project. Past that ceiling, clients drop back to polling on the fetch interval.
Typed parameters that still arrive as strings
The console lets you give every parameter a value type: STRING, BOOLEAN, NUMBER, or JSON. It is easy to read that as typed config. It is not. The type is validation-side only. The parameters docs put it in one sentence: selecting a data type does not affect how values are returned to the client SDKs.
// The console says this parameter is a NUMBER.
// The client still receives a string and has to cast it.
const maxUploads = remoteConfig.getValue("max_uploads").asNumber();
const featureOn = remoteConfig.getValue("checkout_v2").asBoolean();
const banner = remoteConfig.getString("promo_banner_text");
// "Selecting a data type does not affect how values are
// returned to the client SDKs." You own every cast.Every value reaches the client as a string, and every client casts it with getString, getBoolean, getLong, or getValue. The casting burden lives in your code, on every read, on every platform. If a NUMBER parameter ever holds a non-numeric string, the cast silently returns a zero rather than throwing, and you find out in production.
No environments, just more Firebase projects
Remote Config has no first-class concept of an environment. The project best-practices guide is explicit: Firebase recommends a separate Firebase project for each environment in your development workflow. Dev, staging, and prod each become a distinct project.
That has real operational weight:
- Separate credentials and consoles. Three projects means three sets of API keys, three service accounts, and three consoles to switch between.
- Manual promotion.Moving a tested config from staging to prod means copying the template by hand in the console or scripting it through the REST API. There is no "promote" button.
- Drift. Because the projects are independent, they drift. A parameter added in staging and forgotten in prod is a silent gap until something breaks.
Conditions do not solve this. A condition targets a group of app instances by version, platform, device, locale, Analytics audience, or a random percentage. It is per-device targeting, not a deployment environment.
Version history that expires
Remote Config keeps a change history with rollback, both in the console Change history page and through the REST :rollback endpoint. But the templates docs cap it on two axes at once. You get at most 300 stored versions per template type, with the oldest auto-deleted, and each version has a 90-day lifetime.
For a config that changes a few times a week, 300 versions is fine. For a config a team edits daily, you can churn through 300 versions inside the 90-day window and lose the ability to roll back to anything older. If you need an audit trail for compliance, or the option to revert to a state from last quarter, Remote Config will not hold it.
A/B testing means Google Analytics
A/B Testing is free, but only with Google Analytics enabled. Experiments measure results through Analytics goals, so there is no opting out of the data dependency.
- 24 running experiments and rollouts. The cap is shared across both, with 300 experiments total per project over its lifetime.
- Web support arrived in March 2026. A/B testing for Remote Config reached the web years after mobile, so older web setups simply could not run it.
If A/B testing at scale is your core use case, this is where Firebase is strongest, and none of these limits is a reason to leave.
The rough edges developers actually report
Documented limits are one thing. The issues teams file are another, and they cluster around fetch reliability. A sample from the public trackers:
- Throttling at default settings. firebase-android-sdk #5908 reports throttling exceptions in production at the default 12-hour interval.
- Flaky iOS real-time listeners. firebase-ios-sdk #11462 describes the iOS real-time listener failing on roughly one in ten attempts while Android did not.
- Stale values after a restart. firebase-android-sdk #4279 reports activated cached values getting lost across restarts, reverting the app to its in-app defaults.
- Stale fetches on the web. firebase-js-sdk #8233 covers stale fetches in the web SDK.
- A real-time value returning the old value. flutterfire #11104 reported a real-time update handing back the previous value; it was later fixed.
None of these is fatal, and several are resolved. But they fit a pattern: the parts under load are fetch and real-time, and web was the last platform to get attention, lagging mobile on real-time by about two and a half years.
No secrets, by design
One limit is a guardrail rather than a number. The docs warn against storing confidential data in Remote Config parameter keys or values. The values are delivered to clients, so anything you put there is readable by anyone who inspects the app. Remote Config is not a secrets manager, and treating it like one is a security bug waiting to happen.
How the template comes out
If any of the above pushes you toward moving config elsewhere, the data is portable. You can export the full template as JSON three ways, documented in the automation guide: the console download, the Firebase CLI (firebase remoteconfig:get -o filename), or the REST API.
curl --compressed \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-X GET \
"https://firebaseremoteconfig.googleapis.com/v1/projects/<project-id>/remoteConfig" \
-o template.jsonThe shape is a single JSON document with conditions, parameters, parameter groups, and version metadata:
{
"conditions": [],
"parameters": {
"checkout_v2": {
"defaultValue": { "value": "false" },
"conditionalValues": {},
"description": "New checkout flow",
"valueType": "BOOLEAN"
}
},
"parameterGroups": {},
"version": {}
}That file is the input to a migration. BetterConfig can import a Firebase Remote Config template directly: the format is auto-detected, and each parameter keeps its type and description. The full walkthrough lives in the migration guide, and the import flow itself is documented under import and export.
When the limits are the product telling you something
Most of these limits are signals about what Remote Config was built for. It was built to deliver config and run experiments to large mobile audiences, cheaply, at the cost of fast propagation and a first-class environment model. If your pain is A/B testing scale, stay and tune it. The free tier and the experiment tooling are genuinely hard to beat.
If your pain is the config workflow itself, that is a different product category. BetterConfig stores JSON config per environment inside one project, with a real in-app JSON editor, versioned history with no retention cap on any plan, and one-click rollback. Published changes go to an edge-cached read API and are typically live at the edge in under a minute, with no 12-hour interval to wait out. The trade is real and worth naming: BetterConfig has no targeting rules, percentage rollouts, A/B testing, or native SDKs yet. And Firebase reads are free and uncapped, while BetterConfig meters reads at the account level with a hard cap: over the monthly quota the read API returns a 429 with {"error":"reads_limit_exceeded"}. There are never overage charges, upgrading unblocks reads immediately, and the cap resets at the start of each month. If you want the full ledger, read the BetterConfig vs Firebase Remote Config comparison. If you are weighing dedicated flag platforms instead, the ConfigCat vs Flagsmith comparison and the React integration guide go deeper on those paths.