Advanced
LaunchDarkly / Statsig parity features. All are set as options on configure() (or on shipeasy() for SSR), or read through the bound Client.
/docs/ — also served raw at https://shipeasy-ai.github.io/sdk-ts/pages/advanced.md.LaunchDarkly / Statsig parity features. All are set as options on configure()
(or on shipeasy() for SSR), or read through the bound Client.
Exposure control
One (deduped) exposure beacon is logged per enrolled unit. When it fires differs by entrypoint, and both give you a way to suppress it:
-
Server — exposure fires on the first
get()read of an enrolled assignment, not atassign()time. To read without logging (peek), pass{ exposure: false }; the exposure then fires on the next real read.const flags = new Client(user); // construct once per callsite const cta = flags.universe("hero_cta").assign(); // Peek the params WITHOUT logging an exposure: const preview = cta.get("primary_label", "Sign up", { exposure: false }); // …at the moment you actually render the treatment, read normally to log it: render(cta.get("primary_label", "Sign up")); // logs the single exposure (deduped) -
Browser — exposure fires at
assign()time (the visitor is already resolved). Suppress it withassign({ logExposure: false })and re-assign()with logging on at render, or make suppression the default for every read withdisableAutoExposure: trueonconfigure()and opt back in per callsite withassign({ logExposure: true }).const flags = new Client(visitor); // construct once per callsite const cta = flags.universe("hero_cta").assign({ logExposure: false }); render(cta.get("primary_label", "Sign up")); flags.universe("hero_cta").assign(); // …log the exposure at render (deduped per session)
Either way the exposure is deduped per session and durably per
(unit, experiment, group) server-side.
Private attributes
Attributes you mark private are used for evaluation but stripped from every
outbound track() payload (LD/Statsig privateAttributes):
configure({
apiKey: process.env.SHIPEASY_SERVER_KEY!,
privateAttributes: ["email", "ip"],
});On the browser they are still sent to /sdk/evaluate under private_attributes
so the edge can evaluate with them (unavoidable), but they never appear in
tracked event props.
bucketBy — custom bucketing unit
Bucketing hashes on user_id (falling back to anonymous_id) by default. An
experiment can carry its own bucketBy (e.g. company_id) so all users in a
company get the same variant — set on the experiment in the dashboard, no SDK
change needed. When bucketBy is set, the value of that attribute is the hash
unit; if it's missing on the user, evaluation falls back to the standard
identifier. Make sure your attributes transform surfaces the bucketing
attribute:
configure({
apiKey: process.env.SHIPEASY_SERVER_KEY!,
attributes: (u: MyUser) => ({ user_id: u.id, company_id: u.orgId }),
});Sticky bucketing
Lock a unit into the first variant it was assigned even if the experiment's
allocation later changes. On the server, pass a stickyStore to configure()
to persist assignments across requests:
import { configure, createInMemoryStickyStore } from "@shipeasy/sdk/server";
configure({
apiKey: process.env.SHIPEASY_SERVER_KEY!,
stickyStore: createInMemoryStickyStore(), // or a cookie-bridge over __se_sticky
});createInMemoryStickyStore() is process-local (good for a single-process server
or tests); for a multi-process deployment back the StickyBucketStore interface
(get(unit) / set(unit, exp, entry)) with shared storage or a request-cookie
bridge. In the browser, sticky bucketing is on by default (persisted in the
__se_sticky cookie so SSR server eval and the browser agree).
Anonymous-id bucketing
The browser persists an anonymous_id (and the SSR bootstrap mints a matching
__se_anon_id cookie) so a logged-out visitor buckets identically before
and after the server pre-evaluation — no flag flicker on first paint.
Change listeners
The package-level onChange() fires after a background poll returns new data
(HTTP 200, not 304) and returns an unsubscribe callable. It requires
configure({ poll: true }) (no poll thread runs otherwise) and never fires in
test/offline mode:
import { configure, onChange } from "@shipeasy/sdk/server";
configure({ apiKey: process.env.SHIPEASY_SERVER_KEY!, poll: true });
const unsubscribe = onChange(() => { /* re-evaluate / invalidate cache */ });In the browser, onChange() fires after each identify() / override change.
Devtools overlay
Press Shift+Alt+S on any page running the SDK (or append ?se=1). The
Shipeasy devtools panel mounts in a Shadow DOM overlay and lets you flip every
gate / config / experiment / translation for the current session only —
handy for QA, demos, and bug repro. The overlay is delivered as a hosted script
(se-devtools.js) — nothing to install, see the Browser devtools page. React
Native apps get the same panels from the @shipeasy/react-native-devtools
package.