Advanced
LaunchDarkly / Statsig parity features. All are set as options on configure() (or on shipeasy() for SSR), or read through the bound Client.
Generated from the SDK's own /docs/ — also served raw at
https://shipeasy-ai.github.io/sdk/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.
Manual exposure
By default reading an experiment fires one exposure beacon. To control exactly
when the exposure is logged (e.g. only when the treatment actually renders), read
without auto-exposure and call logExposure(name) on the bound Client:
const flags = new Client(req.user); // construct once per callsite
// Read the params, then…
const { params } = flags.getExperiment("hero_cta", { primary_label: "Sign up" });
// …log the exposure at the moment you actually render the treatment:
flags.logExposure("hero_cta");On the server logExposure(name) re-evaluates enrolment for the bound user and
emits the exposure (no-op when the user isn't enrolled). In the browser, set
autoExposure: false on configure() to make manual exposure the default for
every read.
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.