Go
The Shipeasy Go server SDK — context-aware client, local evaluation, configs, kill switches, and metric tracking.
/docs/ — the same Markdown shipeasy docs get --sdk go overview returns, served raw at https://shipeasy-ai.github.io/sdk-go/pages/overview.md. Edit it in the SDK repo, not here.github.com/shipeasy-ai/sdk-go is the server-side Go SDK for
Shipeasy: feature flags ("gates"), dynamic configs,
kill switches, A/B experiments, metric tracking, and structured error reporting.
Evaluation is local against a cached copy of the edge blobs — there is no
network call on the hot path.
Install
go get github.com/shipeasy-ai/sdk-goFull wiring — frameworks, options, env vars — is in Installation.
Quickstart
package main
import (
"os"
shipeasy "github.com/shipeasy-ai/sdk-go"
)
func main() {
// 1) Once, at process start. The api key lives here.
shipeasy.Configure(shipeasy.Options{
APIKey: os.Getenv("SHIPEASY_SERVER_KEY"),
})
// 2) Per request: bind the user once, then call with NO user argument.
c := shipeasy.NewClient(shipeasy.User{"user_id": "u_123", "plan": "pro"})
if c.GetFlag("new_checkout") {
// new behaviour
}
}Mental model: configure once, bind per request
The whole SDK is exactly two calls:
Configure(Options{...})— call it once at process start. The api key lives here, and it kicks off a background fetch so the first read resolves against real rules. It is first-config-wins (idempotent).NewClient(user)— a cheap, user-bound handle you build per request. It carries no api key and opens no connection; every read is local. Its methods take no user argument (the user is already bound):GetFlag,GetFlagOr,GetFlagDetail,GetConfig,GetConfigOr,Universe(name).Assign(),GetKillswitch, plusTrack(event, props).
So an experiment is end-to-end Client-only: bind → Universe(name).Assign() →
Track. Assign() auto-logs a single deduped exposure when the unit is enrolled.
c := shipeasy.NewClient(acct) // acct is your own *Account
if c.GetFlag("new_checkout") { /* ... */ }If you don't supply an Attributes transform (see Installation),
the value you pass to NewClient is assumed to already BE the attribute map, so
shipeasy.NewClient(shipeasy.User{"user_id": "u_123", "plan": "pro"}) works
as-is. NewClient panics if Configure was not called first (the api key
lives in the global config — failing loudly surfaces the misconfiguration).
Feature pages
- Installation —
go get, per-framework wiring, the globalConfigure()call + options table. - Configuration —
Configureoptions, env vars, init/poll vs one-shot, change listeners. - Flags —
GetFlag,GetFlagOr,GetFlagDetail. - Configs —
GetConfig,GetConfigOr. - Kill switches —
GetKillswitch, named switches. - Error reporting — the
See()surface. - Testing —
ConfigureForTesting,ConfigureForOffline, theOverride*helpers. - OpenFeature —
NewGlobalProvider(). - Advanced — manual exposure, private attributes, bucketBy, sticky bucketing, anon-id middleware.
The blocks below are the SDK repo's own snippets — the same ones shipeasy docs get --sdk go release/flags returns, with a worked example baked in.
Feature flags
Read the new_checkout gate for the bound user. Assumes Configure() ran at startup — see Installation.
Read a flag
// construct once per callsite (cheap; binds the user)
c := shipeasy.NewClient(shipeasy.User{"user_id": "u_123"})
// GetFlag(name) — name is the gate key; returns false if the gate is
// absent, disabled, or killswitched (never a user argument — it's bound).
if c.GetFlag("new_checkout") {
// new behaviour
}Flag with an explicit fallback
// construct once per callsite (cheap; binds the user)
c := shipeasy.NewClient(shipeasy.User{"user_id": "u_123"})
// GetFlagOr(name, def) — def is returned ONLY when the flag can't be
// evaluated (engine not ready, or the gate is absent); a gate that
// evaluates false returns false.
on := c.GetFlagOr("new_checkout", true) // name; def returned only on can't-evaluate
_ = onDynamic configs
Read the billing_copy dynamic config (typed JSON value). Assumes Configure() ran at startup — see Installation.
Read a config
// construct once per callsite (cheap; binds the user)
c := shipeasy.NewClient(shipeasy.User{"user_id": "u_123"})
// GetConfig(name) — name is the config key; returns (value any, ok bool).
// ok is false when the key is absent. Type-assert value to what you stored.
if cfg, ok := c.GetConfig("billing_copy"); ok {
m := cfg.(map[string]any) // configs are arbitrary JSON
_ = m["cta"]
}Config with an explicit fallback
// construct once per callsite (cheap; binds the user)
c := shipeasy.NewClient(shipeasy.User{"user_id": "u_123"})
// GetConfigOr(name, def) — def is returned when the key is absent.
v := c.GetConfigOr("billing_copy", map[string]any{"cta": "Buy"}) // name; def
_ = vKill switches
Check whether the payments kill switch is engaged. Assumes Configure() ran at startup — see Installation.
Read a kill switch
// construct once per callsite (cheap; binds the user)
c := shipeasy.NewClient(shipeasy.User{"user_id": "u_123"})
// GetKillswitch(name) — name is the kill-switch key; true means engaged
// (the feature is killed). Returns false if the switch is absent.
if c.GetKillswitch("payments") {
// feature is killed — short-circuit
}Read a named per-key switch
// construct once per callsite (cheap; binds the user)
c := shipeasy.NewClient(shipeasy.User{"user_id": "u_123"})
// GetKillswitch(name, switchKey) — the optional switchKey selects a named
// per-key override (the dashboard "switches" feature). When that key has no
// override, it falls back to the kill switch's top-level value.
if c.GetKillswitch("payments", "eu") {
// killed for the "eu" variant
}Track a conversion
Track a metric/conversion event from the bound Client. Metrics in the dashboard
are computed from these events. Assumes Configure() ran at startup — see
Installation.
Track an event
// construct once per callsite (cheap; binds the user)
c := shipeasy.NewClient(shipeasy.User{"user_id": "u_123"})
// Track(event, props)
// event — the event your metric is built on (required)
// props — optional payload; numeric/string fields you can sum/filter on in a
// metric (private attributes are stripped before egress)
c.Track("checkout_started", map[string]any{"amount": 49, "currency": "usd"})Fire-and-forget (never blocks your response) and a no-op under
ConfigureForTesting / ConfigureForOffline. The unit is the bound user
(user_id, else anonymous_id); with no unit the call is a no-op.
Track without properties
// construct once per callsite (cheap; binds the user)
c := shipeasy.NewClient(shipeasy.User{"user_id": "u_123"})
c.Track("checkout_started", nil) // props are optional — pass nil