Skip to content

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

js
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:

js
const variant = window.periscale.getVariant("hero_layout"); // "control" | "v2" | undefined
const flag = window.periscale.getFeatureFlag("hero_layout"); // string | boolean | undefined
  • getVariant(key) returns the variant string (or undefined for 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:

js
const off = window.periscale.onFeatureFlags((flags) => {
  console.log("flags ready:", flags);
});
// later:
off(); // unsubscribe

The 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:

js
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

  1. On init(), the tag POSTs { api_key, tenant, distinct_id, properties } to /decide.
  2. The response is a { featureFlags: { … } } map, cached in localStorage.
  3. 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.

© Periscale