Shipeasy

SDKs

One package for JavaScript (server + browser) plus seven native server languages — all evaluating the same blob, the same way.

SDKs

One evaluation model, every language.

The TypeScript package ships a server and a browser build. Seven native server SDKs — Go, Python, Ruby, Java, Kotlin, PHP, Swift — speak the same wire format and run the same deterministic evaluation locally.

Production readyOn this page · 6 min readUpdated · June 18, 2026Works with · TS · JS · React · Go · Python · Ruby · Java · Kotlin · PHP · Swift

Every Shipeasy SDK is a thin client over two cached blobs — :flags (gates + configs + kill switches) and :experiments (universes + experiments). The SDK polls them in the background and evaluates locally: there is no per-call network round-trip on your hot path.

The shared evaluation model

All SDKs implement the same contract, so a flag buckets identically whether the call runs in Node, Go, or Swift:

  • Deterministic bucketing. The rollout bucket is murmur3("salt:unit") % 10000. The hash is the cross-language MurmurHash3 x86_32 variant (seed 0), verified against shared test vectors — the same user_id lands in the same bucket in every language.
  • Local evaluation. Targeting rules, rollout, holdouts, and experiment allocation all run in-process against the polled blob. See Feature gates for the rule model.
  • One unit, server + browser. Logged-out traffic buckets on a shared first-party __se_anon_id cookie, so the server and the browser bucket a visitor the same way.
  • ETag polling. Each poll sends If-None-Match; a 304 skips the parse. The poll interval is plan-driven and pushed via the X-Poll-Interval header.
The Node SDK is canonical

When a native SDK and the TypeScript SDK disagree, the TypeScript SDK is the reference implementation. Native pages stay idiomatic to their language but never add method names that contradict it.

Configure once, bind a client

Every SDK leads with the same ergonomic flow: a one-time global configure(...) (server key or client key, plus an optional attributes transform that maps your user object onto the Shipeasy attribute map), then a lightweight, user-bound Client whose getters take no user argument.

import { configure, Client } from "@shipeasy/sdk/server";

configure({
  apiKey: process.env.SHIPEASY_SERVER_KEY!,
  attributes: (u) => ({ user_id: u.id, plan: u.plan }),
});

const flags = new Client(currentUser);
flags.getFlag("new_checkout");

The heavyweight polling state lives in an Engine behind configure() — engine-level features (testing, offline snapshots, onChange, manual exposure, private attributes, sticky bucketing) are configured there. The older per-call getFlag(name, user) form still works on the Engine directly for back-compat.

What every SDK supports

CapabilityBound-client shapeNotes
Feature flagsgetFlag(name[, default])Boolean; default returned only when unevaluable
Dynamic configsgetConfig(name[, default])Typed JSON value with targeting
ExperimentsgetExperiment(name, defaultParams){ inExperiment, group, params }
Evaluation detailgetFlagDetail(name){ value, reason } — see Reasons
Event trackingtrack(event, props)Fire-and-forget metric ingestion
Change listenersEngine.onChange(cb)Fires after a poll brings new data — see onChange
Offline snapshotEngine.fromFile / fromSnapshotZero-network eval against a captured blob
Test utilitiesEngine.forTesting() + override*No key, no network — see Testing

Defaults are returned only when a value can't be evaluated (client not ready, or the key isn't in the blob). A flag that legitimately evaluates false returns false, never the default.

Pick your language

Was this page helpful?
✎ Edit this page

On this page