Feature flags & experiments
Evaluate server-side flags and multivariate variants in the browser without a page reload. Flags are fetched once on boot, cached locally, and re-evaluated on demand.
Flags are configured per business in the dashboard. The tag reads them from the /decide endpoint; everything below is the client-side API.
Check a boolean flag
if (window.periscale.isFeatureEnabled("new_checkout")) {
showNewCheckout();
}isFeatureEnabled(key) returns true only when the flag is explicitly enabled. An unknown or undefined flag returns false. String values of "false" or "" are treated as disabled.
Read a variant (A/B tests)
For multivariate experiments, read the assigned variant:
const variant = window.periscale.getVariant("hero_layout"); // "control" | "v2" | undefined
const flag = window.periscale.getFeatureFlag("hero_layout"); // string | boolean | undefinedgetVariant(key)returns the variant string (orundefinedfor boolean flags).getFeatureFlag(key)returns the raw value — string for variants, boolean for flags.
React to changes
Flags load asynchronously. If a flag isn't ready when your code runs, subscribe to be notified once they resolve:
const off = window.periscale.onFeatureFlags((flags) => {
console.log("flags ready:", flags);
});
// later:
off(); // unsubscribeThe callback fires immediately with the current flags if they're already loaded, then again whenever flags are re-evaluated.
Force a refresh
Flags are cached for 60 seconds in localStorage (key p_flags_v1) and scoped to the current distinct_id. Call reloadFeatureFlags() to bypass the cache — for example, right after identify() so the new identity gets fresh targeting:
window.periscale.identify("user-123", { plan: "pro" });
await window.periscale.reloadFeatureFlags();Exposure tracking
Every time you read a flag, the tag automatically emits a $feature_flag_called event carrying the flag key and the value served. This is what lets you attribute conversions to the variant a visitor saw. Each (flag, value) pair fires once per page load, so reading the same flag repeatedly doesn't spam events.
How evaluation works
- On
init(), the tag POSTs{ api_key, tenant, distinct_id, properties }to/decide. - The response is a
{ featureFlags: { … } }map, cached inlocalStorage. - Subsequent reads hit the cache until the 60s TTL expires or you call
reloadFeatureFlags().
The request is best-effort: if /decide is unreachable, every flag resolves to its default (false / undefined) and your code degrades gracefully.