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
npm install @sankofa/switchRegister the plugin
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
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:
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.:
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):
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:
flags.onChange("new_checkout", (decision) => {
if (decision.reason === "halted") {
showInternalBanner("new_checkout halted");
}
});API summary
| Symbol | Description |
|---|---|
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. |