Web (multi-package)

@sankofa/switch

Feature flags + per-call exposure tracking for the web. getFlag, getVariant, getDecision, halt-aware decisions with bundled defaults and onChange subscriptions.

@sankofa/switch is the web SDK's feature-flag client. It plugs into @sankofa/browser and consumes the switch slice of the decision handshake, then exposes a synchronous lookup API.

The web Switch package also does per-call exposure tracking — every getFlag/getVariant call records that the user actually evaluated the flag, deduplicated per session per (key, value, variant). This is the difference between "the user was assigned" and "the user actually saw" — see Exposure tracking.

Install

bash
npm install @sankofa/switch

Register the plugin

TypeScript
import { Sankofa } from "@sankofa/browser";
import { switchPlugin, getSwitch } from "@sankofa/switch";

await Sankofa.init({
apiKey: "sk_live_...",
endpoint: "https://api.sankofa.dev",
plugins: [
  switchPlugin({
    defaults: {
      new_checkout: false,
      dark_mode_default: false,
    },
  }),
],
});

const flags = getSwitch()!;

getSwitch() returns the singleton flag client. It's null until Sankofa.init resolves; after that it's stable.

Boolean flags

TypeScript
if (flags.getFlag("new_checkout")) {
renderNewCheckout();
} else {
renderClassicCheckout();
}

getFlag(key, defaultValue?) returns a boolean. The default is the value passed to switchPlugin({ defaults: {...} }) at init time, or false if not set, or whatever you pass as the second argument.

Variant flags

Flags can have variants — control, treatment_a, treatment_b, etc. Use getVariant:

TypeScript
const variant = flags.getVariant("checkout_redesign", "control");

switch (variant) {
case "treatment_a": return <CheckoutA />;
case "treatment_b": return <CheckoutB />;
default: return <ClassicCheckout />;
}

Always handle the unknown-variant case (default) so a future variant added in the dashboard doesn't break your code.

Reading the full decision

getDecision returns the value plus metadata — the rollout reason, the variant, etc.:

TypeScript
const decision = flags.getDecision("new_checkout");
//   { value: true, variant: null, reason: "rollout" }

console.log(decision?.reason); // "rollout", "cohort:pro", "default", "halted", etc.

The reason field is documented in The decision handshake.

Subscribing to changes

For long-lived UIs that need to refresh when a flag flips mid-session (rare but real — usually because of a halt-webhook fire):

TypeScript
const unsubscribe = flags.onChange("new_checkout", (decision) => {
rerenderUI(decision.value);
});

In React, wrap this in useState + useEffect. See @sankofa/react for the recommended pattern.

Halt-aware decisions

When a flag is halted (manually or via halt-webhook), the engine immediately broadcasts the halt to every connected client. The next getFlag call returns the default value with reason: "halted". Use this to surface admin-facing UI when an experiment is paused:

TypeScript
flags.onChange("new_checkout", (decision) => {
if (decision.reason === "halted") {
  showInternalBanner("new_checkout halted");
}
});

API summary

SymbolDescription
switchPlugin({ defaults? })Plugin to register at Sankofa.init.
getSwitch()Returns the singleton flag client.
flags.getFlag(key, default?)Boolean feature flag.
flags.getVariant(key, default?)Variant feature flag.
flags.getDecision(key)Full decision envelope (value + variant + reason).
flags.onChange(key, listener)Subscribe to mid-session changes.

What's next

Edit this page on GitHub