When a device fetches a config
Remote configs are fetched lazily: nothing is loaded at app start. The SDK requests configs when your code callsremoteConfig() / remoteConfigList() (or their equivalents on wrapper SDKs).
Two things to know about the fetch:
- 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. - 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
identifycall, 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+).
- 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
How one config is chosen among several
When several configurations share a context key, the server picks at most one:- 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.
- 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.
- For each candidate the server checks targeting rules (
segmentation_conditions, including audience references) and then the rollout percentage (segment_percent).
- 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.
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’sRemoteConfigurationSource.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.
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.
- 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.
- 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 onidentify(...) 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+,
identifydrops 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: callinvalidateRemoteConfigsCache()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
identifywith 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, callinvalidateRemoteConfigsCache().