Shipeasy
SDKsReferenceTypeScript / JavaScript

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-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 at assign() 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 with assign({ logExposure: false }) and re-assign() with logging on at render, or make suppression the default for every read with disableAutoExposure: true on configure() and opt back in per callsite with assign({ 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.

Was this page helpful?
Updated July 26, 2026

On this page