Shipeasy

Go

The Shipeasy Go server SDK — context-aware client, local evaluation, configs, kill switches, and metric tracking.

Production readyOn this page · 5 min readUpdated · August 9, 2026Works with · Go 1.21+
Generated from the Go SDK repo's own /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-go

Full 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:

  1. 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).
  2. 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, plus Track(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

  • Installationgo get, per-framework wiring, the global Configure() call + options table.
  • ConfigurationConfigure options, env vars, init/poll vs one-shot, change listeners.
  • FlagsGetFlag, GetFlagOr, GetFlagDetail.
  • ConfigsGetConfig, GetConfigOr.
  • Kill switchesGetKillswitch, named switches.
  • Error reporting — the See() surface.
  • TestingConfigureForTesting, ConfigureForOffline, the Override* helpers.
  • OpenFeatureNewGlobalProvider().
  • 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
_ = on

Dynamic 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
_ = v

Kill 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
Was this page helpful?
Updated July 26, 2026

On this page