Flags, config & experiments
Evaluate feature flags, read remote config, run rollouts and experiments with deterministic, offline-safe bucketing that matches web, mobile, and backend byte-for-byte.
Flags, remote config, rollouts, and experiments share one feature. Bucketing is
computed locally and deterministically (fnv1a32), so the same user lands in the
same variant on the web, in your app, and on your backend — and it works
offline, with SSR bootstrap and live updates over SSE.
Feature flags#
if (ScaleBun.flags.isEnabled('new-checkout')) { renderNewCheckout();}
const color = ScaleBun.flags.variation('cta-color', '#111');Wait for the first flag payload when you must:
await ScaleBun.flags.ready();variationDetail returns why a user was served what they were served, which is
what lets you segment exposures by reason rather than just by value:
const { value, variant, reason } = ScaleBun.flags.variationDetail('cta-color', '#111');// reason: 'TARGETING_MATCH' | 'ROLLOUT' | 'DEFAULT' | 'FLAG_NOT_FOUND' | …Reacting to changes#
Flag edits and kill-switches arrive over a realtime stream within seconds. That updates the cache — to update the screen, subscribe:
const unsubscribe = ScaleBun.flags.onChange(() => rerender());React has a hook for this (useFlag); other frameworks wire it by hand. See
Integrations.
No flash of default#
On a cold cache the first paint happens before the config request returns, so a flagged element renders its fallback and then swaps — the flicker users notice. Seed the SDK from your server render to remove it:
await ScaleBun.init({ clientKey, apiBaseUrl, flags: { bootstrap: serverConfigPayload, // same shape as GET /config realtime: true, // default; false = poll only, no stream },});Experiments and rollouts#
const arm = ScaleBun.experiments.variant('pricing-test'); // 'control' | 'treatment' | nullconst on = ScaleBun.rollouts.isOn('beta-banner'); // percentage rollout / kill-switchExposures#
Reading a flag records an exposure once per (flag, value) per session: it stamps
$feature/<key> as a super-property, so every later event carries the variant
without a join, and emits one $feature_flag_called.
Reads are cheap and idempotent — call them at the point of use rather than caching the value in your own state, or the exposure will not line up with what the user actually saw.
Remote config#
Read dashboard-set values at runtime without shipping a release. A fallback is required — it is the value used before the first fetch, when offline, when the key is missing, or when the stored value fails to coerce:
const maxItems = ScaleBun.config.get('max_items', 10);Overrides (QA)#
ScaleBun.flags.override('cta-color', 'blue'); // persisted, for local testingScaleBun.flags.clearOverride('cta-color');ScaleBun.flags.clearOverrides(); // drop all of themOverrides persist across reloads, which is what makes them useful for QA and
also what makes them easy to forget — clearOverrides() is the reset.
Remote config refresh#
await ScaleBun.config.refresh(); // force a re-read, e.g. after a context changeNext#
Product analytics — measure what a variant changed.
Engagement — surveys and campaigns by cohort.