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 its targeting rules (
segmentation_conditions).
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.- 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 — users who already received the configuration keep their automatic assignment.
- Disabling freeze is one-way: pins created while it was on stay pinned (they are indistinguishable from pins you made yourself). Only new recipients get automatic assignments 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 happens to existing assignments when targeting changes
Editing a configuration’s targeting rules, reordering priorities, or activating another configuration for the same context key does not move users who already hold an assignment for that context key — automatic or manual. On their next request they keep the configuration they have. The current rules and priority order apply to every user who has no assignment for that context key:- new users, and users who did not match any configuration before;
- users whose configuration was archived or deleted;
- users you detached from their configuration.
- Payload edits are live. The payload is read fresh on every request, so attached users see new values on their next fetch.
- Archiving or deleting a configuration releases everyone attached to it immediately.
Changing experiment traffic mid-flight
Experiment assignments are persisted per user — including rejections — so a traffic change never revisits a stored decision:- 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.
- 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):
- 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 fresh fetch from the server in that state, callinvalidateRemoteConfigsCache().