Debug why a user isn't seeing a feature
Use getFlagDetail reasons to tell DEFAULT (rolled to 0%) from FLAG_NOT_FOUND (typo or stale bundle) from OFF (killed).
A customer says the new dashboard isn't showing up. Through getFlag all you see is false — which tells you nothing about why. Did the gate roll out to 0%? Did someone mistype the name? Is the bundle stale? Is the gate killed? Every one of those reads as the same false, and you're left guessing.
getFlagDetail returns { value, reason } from the same single evaluation, so the cause is right there.
import { configure, Client } from "@shipeasy/sdk/server";
configure({ apiKey: process.env.SHIPEASY_SERVER_KEY! });
const flags = new Client({ user_id: "u_4f2a" });
const d = flags.getFlagDetail("new_dashboard");
// → { value: false, reason: "DEFAULT" }Read the reason
| reason | what it means for your bug |
|---|---|
DEFAULT | the gate exists and evaluated false — likely rolled out to 0% |
FLAG_NOT_FOUND | the name isn't in the loaded rules — typo or a stale bundle |
OFF | the gate is disabled / killed (server only) |
OVERRIDE | a local or ?se_gate_… override decided it, not the real rules |
RULE_MATCH | it evaluated true — the feature is on for this user |
CLIENT_NOT_READY | the SDK answered with your default because nothing had loaded yet |
The two that look identical through getFlag are the two that matter most here: DEFAULT means "the gate is real, this user just isn't in the rollout"; FLAG_NOT_FOUND means "this name resolves to nothing" — a typo or a client running an old blob.
getFlagDetail is the same evaluation as getFlag, with the reason kept
instead of discarded. The reason is computed at the client boundary — drop it into your structured
logs and the next "why isn't this on?" answers itself.
On the browser, the edge pre-evaluates the killed state into a boolean, so OFF folds into DEFAULT — OFF is a server-only reason.
Rollout & measurement plan
- Log the reason, not just the value. A line of structured logging with
reasonturns every future "missing feature" report into a lookup instead of an investigation. FLAG_NOT_FOUNDin prod = ship hygiene. It almost always means a typo or a client that hasn't picked up the latest rules — check the name against the dashboard and the bundle's freshness.CLIENT_NOT_READYmeans you read too early. Make sureinit()(server) oridentify()(browser) resolved before the read, or accept the caller default knowingly.
Target on email or PII without it landing in analytics
Use privateAttributes to target on email or tier while stripping those fields from every event the SDK emits.
Invalidate a server cache the instant a config flips
Use onChange to drop a memoized render or a derived config on the next poll, so a dashboard change takes effect without a restart.