Skip to main content
This page describes the evaluation model behind Remote Configs — what happens on the device and on the server when your app asks for a config, and what changing targeting rules does to users who already received one.

When a device fetches a config

Remote configs are fetched lazily: nothing is loaded at app start. The SDK requests configs when your code calls remoteConfig() / remoteConfigList() (or their equivalents on wrapper SDKs). Two things to know about the fetch:
  1. Pending user properties are flushed first. Before every remote config request the SDK force-sends any user properties waiting in its debounce queue and only then calls the config endpoint — so a config requested right after setUserProperty(...) is evaluated with that property already stored. The server reads properties synchronously at request time; there is no replication lag.
  2. Responses are cached in memory per context key for the lifetime of the process. A cached config is served without a network call. The cache is dropped when:
    • the user changes (identify with a different user id, logout) — and on iOS 6.14.0+, Android 9.7.0+, and the wrapper versions listed below, on any identify call, even with the same id;
    • you call attachUserToRemoteConfiguration / detachUserFromRemoteConfiguration (or the experiment counterparts) — all cached configs are invalidated, including named context keys;
    • you call invalidateRemoteConfigsCache() explicitly (iOS 6.14.0+, Android 9.7.0+, React Native 10.11.0+, Flutter 11.10.0+, Cordova 7.9.0+, Capacitor 1.8.0+, Unity 9.9.0+).
If the network request fails, the SDK can serve a bundled fallback file. Fallbacks are never cached — the next call retries the network.

How one config is chosen among several

When several configurations share a context key, the server picks at most one:
  1. Experiments win first. If the user is assigned to a group of a non-finished experiment for that context key, the experiment’s payload is returned. Finished experiments release their users.
  2. Remote configurations are evaluated in priority order — the order you set on the Remote Configs list (drag-and-drop in the dashboard). The first configuration whose targeting matches the user wins; users who don’t match fall through to the next one.
  3. For each candidate the server checks targeting rules (segmentation_conditions, including audience references) and then the rollout percentage (segment_percent).
The rollout percentage is deterministic: each user is bucketed by a stable hash of the configuration and their user id into 0–99 and admitted when the bucket is below the percent. Consequences:
  • The same user gets the same in/out decision on every evaluation — a config edit never reshuffles who is inside the rollout.
  • Raising the percent only adds users (the newly admitted top buckets); lowering it removes only the users in the topmost buckets.
If nothing matches, the SDK gets no config for that context key.

Assignments: why evaluation is sticky

The first evaluation that matches a configuration is persisted as an assignment (attachment) for that user and context key. Later requests serve the stored assignment directly — the targeting rules are not re-run on every request. This is what makes delivery cheap and consistent: a user who saw a price or a paywall keeps seeing it. There are two assignment types, reported in the SDK’s RemoteConfigurationSource.assignmentType:
  • Automatic — assigned by matching targeting rules.
  • Manual — pinned explicitly via POST /remote-configurations/{config_id}/users/{user_id} or the SDK attach methods. A manual pin overrides targeting rules and is never re-evaluated — it survives config edits until you detach it, archive the config, or delete it.

Freezing assignments

A configuration can be frozen with the Freeze assignments switch in the dashboard’s Targeting card. While frozen, every user who newly receives the configuration is stored as a manual pin instead of an automatic assignment — so later targeting, rollout, or priority changes never re-seat them (nothing in the re-evaluation list below applies to manual pins).
  • Enabling freeze offers a one-shot option to also pin the users who already received the configuration automatically. Skip it, and only future recipients are pinned — existing automatic assignments stay re-evaluable.
  • Disabling freeze is one-way: pins created while it was on stay pinned (they are indistinguishable from pins you made yourself). Only new deliveries become re-evaluable again.
  • Payload edits still apply to pinned users — freezing controls who keeps the config, not what they receive.
  • To release pinned users, detach them individually or archive/delete the configuration.
Freeze-made pins report assignmentType: manual, exactly like your own pins.

What re-evaluates an automatic assignment

Automatic assignments are re-evaluated when the targeting they were computed against changes. Any of these bumps the configuration’s targeting epoch:
  • editing the configuration’s targeting rules or segment_percent;
  • publishing or restoring a configuration version;
  • reordering priorities within the context key;
  • editing an audience referenced by the configuration’s rules — this re-evaluates every configuration that references it.
On the user’s next request after such a change, the server evaluates them against the current rules:
  • still matching → they keep the configuration (nothing changes);
  • no longer matching → they migrate to the next matching configuration in priority order, or to none.
Gradual rollout. Re-evaluation is being enabled project by project. On projects not yet migrated, automatic assignments are never re-evaluated: targeting edits apply only to users who have no assignment yet (new users, or after a manual detach). Manual pins behave the same on every project. Contact support if you want your project migrated.
Two changes apply without any re-evaluation:
  • Payload edits are live. The payload is read fresh on every request, so attached users see new values on their next fetch regardless of re-evaluation.
  • Archiving or deleting a configuration releases everyone attached to it immediately.

Changing experiment traffic mid-flight

Experiment assignments are persisted per user — including rejections — which makes experiment traffic changes asymmetric with remote-config rollouts:
  • Raising the traffic allocation admits only users the experiment has never evaluated. A user previously rejected by the traffic gate was excluded persistently and is not re-admitted — the monotonic-ramp property of remote-config rollouts does not extend to experiments.
  • Lowering the traffic allocation evicts no one: users already assigned keep their group; the new percent gates only first-time evaluations.
  • Changing the group split (e.g. 50/50 → 70/30) never migrates existing assignments — group membership is drawn at admission and stored. The new weights apply to newly admitted users only.
  • Finishing the experiment is the only change that releases everyone at once.

User properties in targeting

User properties set via the SDK participate in targeting rules (user_properties conditions) and in audience definitions:
  • Properties are batched on the device with a ~5 second debounce, then sent in one request. Backgrounding the app flushes them immediately.
  • As covered above, the SDK flushes pending properties before every remote config fetch and waits for that flush — the “property race” does not exist on the config path: what you set is what the evaluation sees.
  • If a flush fails, the SDK retries with backoff; a cached config served from memory still triggers the flush, so properties are not swallowed by a cache hit.

Identify and assignments

What happens on identify(...) depends on whether the user id changes:
  • First login (identity created for the current user): the id stays the same. On iOS 6.14.0+, Android 9.7.0+, React Native 10.11.0+, Flutter 11.10.0+, Cordova 7.9.0+, Capacitor 1.8.0+, and Unity 9.9.0+, identify drops the config cache even when the id doesn’t change — the next fetch evaluates fresh against that user’s stored assignments and properties. On older SDK versions nothing is re-evaluated by itself: call invalidateRemoteConfigsCache() or simply wait for the next cold start.
  • Returning user / second device (identity already exists): the SDK switches to the existing user id, drops the config cache, and the next fetch evaluates fresh — against the assignments and properties of that user. Anonymous-user assignments do not transfer to the identified user.
  • Logout: the SDK reverts to the original anonymous user of this device (the one active before the first identify — no new user is created); the cache is dropped.
  • Repeat identify with the already-attached identity: a no-op on the SDK side — no network call, no cache drop. To force a re-evaluation in that state, call invalidateRemoteConfigsCache().

Summary