Go SDK

View as Markdown

The Unleash Go SDK lets you evaluate feature flags in your Go services and applications.

You can use this SDK with Unleash Enterprise or Unleash Open Source.

Requirements

  • Go 1.21 or later (the SDK is tested against Go 1.21.x through 1.26.x)

Installation

Install the SDK module:

$go get github.com/Unleash/unleash-go-sdk/v6@latest

Configuration

Pass configuration options to unleash.Initialize(...) (global client) or unleash.NewClient(...) (instance client).

WithUrl
func(string) ConfigOptionRequired

Base URL to your Unleash or Edge API, for example: https://<your-unleash-instance>/api/.

WithAppName
func(string) ConfigOptionRequired

Name of your application. Included in registration, metrics, and request headers.

WithCustomHeaders
func(http.Header) ConfigOption

Additional headers for all API requests. Use this to add Authorization.

WithEnvironment
func(string) ConfigOptionDefaults to default

Environment value included in the static Unleash context.

WithInstanceId
func(string) ConfigOption

Explicit instance ID for this SDK process. If omitted, the SDK generates one.

WithRefreshInterval
func(time.Duration) ConfigOptionDefaults to 15

How often (in seconds) the SDK polls for updated flag configuration.

WithMetricsInterval
func(time.Duration) ConfigOptionDefaults to 60

How often (in seconds) the SDK sends usage metrics to Unleash.

WithDisableMetrics
func(bool) ConfigOptionDefaults to false

Disables client registration and metrics posting when set to true.

WithProjectName
func(string) ConfigOption

Restricts flag fetches to a single project. Prefer using a project scoped API token instead.

WithBackupPath
func(string) ConfigOptionDefaults to os.TempDir()

Directory used by storage implementations for persisted SDK state.

WithStorage
func(Storage) ConfigOption

Custom storage implementation for local persistence and bootstrapping. See bootstrap flag data.

WithStrategies
func(...strategy.Strategy) ConfigOption

Registers custom activation strategies. See custom strategies.

WithHttpClient
func(*http.Client) ConfigOption

Custom *http.Client for transport settings. Configure proxy, TLS, and timeouts using the Go standard libraryhttp.Client. This is also how you configure an outbound network proxy.

WithListener
func(any) ConfigOptionDefaults to NoopListener

Listener implementation for SDK events (errors, ready/update, metrics, impression data).

Initialization

Initialize the SDK early in your application’s lifecycle.

1package main
2
3import (
4 "net/http"
5
6 unleash "github.com/Unleash/unleash-go-sdk/v6"
7)
8
9func main() {
10 err := unleash.Initialize(
11 unleash.WithAppName("my-go-service"),
12 unleash.WithUrl("https://<your-unleash-instance>/api/"),
13 unleash.WithCustomHeaders(http.Header{"Authorization": {"<your-backend-token>"}}),
14 )
15
16 if err != nil {
17 panic(err)
18 }
19
20 defer unleash.Close()
21}

Connection options

Backend SDKs communicate with Unleash or Unleash Edge through the Client API and require a backend token. They fetch flag configuration and evaluate flags locally in your application.

Set WithUrl to the base API endpoint of your Unleash or Unleash Edge instance, typically https://<your-unleash-instance>/api/. The URL pattern is the same in both cases. Use Unleash Edge when you need lower latency or higher availability.

Set WithCustomHeaders to include an Authorization header with a backend token. See API tokens for how to generate one.

Configure an HTTP proxy

The SDK uses Go’s standard HTTP proxy environment variables.

Set HTTPS_PROXY or HTTP_PROXY in the environment where your service runs:

$export HTTPS_PROXY=http://proxy.internal:8080
$export NO_PROXY=localhost,127.0.0.1,.internal

Wait until the SDK is ready

Until the SDK loads flag data, boolean checks return false and variant checks return a disabled variant. See Check flags for details on evaluation methods and fallback behavior.

  1. Bootstrap data: if a bootstrap is provided the SDK loads data from it synchronously on creation, if the bootstrap fails to load it will fall back to cached data.
  2. Cached data: if no bootstrap is available, the SDK attempts to load data from persistent storage synchronously on creation.
  3. Network fetch: the SDK fetches flag configuration from Unleash asynchronously on startup. The ready event fires on the first successful response from Unleash.

If bootstrap or cached data is available, the SDK can evaluate flags immediately. There is no need to wait for the network fetch.

Use WaitForReady to block until the SDK has completed its first network fetch. This is useful when you need guaranteed fresh data, but it will block even if bootstrap or cached data is already loaded. If the API is unreachable, WaitForReady blocks indefinitely.

1package main
2
3import (
4 "net/http"
5
6 unleash "github.com/Unleash/unleash-go-sdk/v6"
7)
8
9func main() {
10 err := unleash.Initialize(
11 unleash.WithAppName("my-go-service"),
12 unleash.WithUrl("https://<your-unleash-instance>/api/"),
13 unleash.WithCustomHeaders(http.Header{"Authorization": {"<your-backend-token>"}}),
14 )
15
16 if err != nil {
17 panic(err)
18 }
19 unleash.WaitForReady()
20}

Check flags

Check if a flag is enabled

Use IsEnabled with FeatureOptions:

1package main
2
3import (
4 "net/http"
5
6 unleash "github.com/Unleash/unleash-go-sdk/v6"
7)
8
9func main() {
10
11 err := unleash.Initialize(
12 unleash.WithAppName("my-go-service"),
13 unleash.WithUrl("https://<your-unleash-instance>/api/"),
14 unleash.WithCustomHeaders(http.Header{"Authorization": {"<your-backend-token>"}}),
15 )
16 if err != nil {
17 panic(err)
18 }
19
20 enabled := unleash.IsEnabled("my-feature", unleash.FeatureOptions{})
21 if enabled {
22 // new behavior
23 }
24}

FeatureOptions controls how IsEnabled evaluates a flag:

Ctx
context.Context

Evaluation context for this call. See Unleash context for how to pass the same context to both IsEnabled and GetVariant.

Fallback
*bool

Fallback value used when the flag cannot be resolved and FallbackFunc is not set.

FallbackFunc
func(feature string, ctx *context.Context) bool

Fallback callback used when the flag cannot be resolved. If set, this takes precedence over Fallback.

Resolver
func(feature string) *api.Feature

Override for resolving flags without repository lookup. This exists for backwards compatibility and is usually not needed in v6.

Fallback precedence when a flag is missing:

  1. FallbackFunc
  2. Fallback
  3. false

Example with fallback:

1package main
2
3import (
4 "net/http"
5
6 unleash "github.com/Unleash/unleash-go-sdk/v6"
7 unleashcontext "github.com/Unleash/unleash-go-sdk/v6/context"
8)
9
10func main() {
11
12 err := unleash.Initialize(
13 unleash.WithAppName("my-go-service"),
14 unleash.WithUrl("https://<your-unleash-instance>/api/"),
15 unleash.WithCustomHeaders(http.Header{"Authorization": {"<your-backend-token>"}}),
16 )
17 if err != nil {
18 panic(err)
19 }
20
21 enabled := unleash.IsEnabled("my-feature", unleash.FeatureOptions{
22 FallbackFunc: func(feature string, _ *unleashcontext.Context) bool {
23 return true
24 },
25 })
26
27 if enabled {
28 // New behaviour
29 }
30}

Check a flag’s variant

Use GetVariant with VariantOptions:

1package main
2
3import (
4 "net/http"
5
6 unleash "github.com/Unleash/unleash-go-sdk/v6"
7)
8
9func main() {
10 err := unleash.Initialize(
11 unleash.WithAppName("my-go-service"),
12 unleash.WithUrl("https://<your-unleash-instance>/api/"),
13 unleash.WithCustomHeaders(http.Header{"Authorization": {"<your-backend-token>"}}),
14 )
15 if err != nil {
16 panic(err)
17 }
18 defer unleash.Close()
19
20 variant := unleash.GetVariant("checkout-experiment", unleash.VariantOptions{})
21 if variant.Name == "blue" {
22 // blue variant behavior
23 } else if variant.Name == "green" {
24 // green variant behavior
25 }
26}

VariantOptions controls how GetVariant evaluates variants:

Ctx
context.Context

Evaluation context for this call. See Unleash context for how to pass the same context to both IsEnabled and GetVariant.

VariantFallback
*api.Variant

Fallback variant used when no variant can be resolved and VariantFallbackFunc is not set.

VariantFallbackFunc
func(feature string, ctx *context.Context) *api.Variant

Fallback callback used when no variant can be resolved. If set, this takes precedence over VariantFallback.

Resolver
func(feature string) *api.Feature

Override for resolving flags without repository lookup. This exists for backwards compatibility and is usually not needed in v6.

Fallback precedence when a variant cannot be resolved:

  1. VariantFallbackFunc
  2. VariantFallback
  3. SDK default disabled variant

Example with variant fallback:

1package main
2
3import (
4 "net/http"
5
6 unleash "github.com/Unleash/unleash-go-sdk/v6"
7 "github.com/Unleash/unleash-go-sdk/v6/api"
8 unleashcontext "github.com/Unleash/unleash-go-sdk/v6/context"
9)
10
11func main() {
12 err := unleash.Initialize(
13 unleash.WithAppName("my-go-service"),
14 unleash.WithUrl("https://<your-unleash-instance>/api/"),
15 unleash.WithCustomHeaders(http.Header{"Authorization": {"<your-backend-token>"}}),
16 )
17 if err != nil {
18 panic(err)
19 }
20 defer unleash.Close()
21
22 fallbackVariant := &api.Variant{
23 Name: "fallback",
24 Enabled: true,
25 FeatureEnabled: true,
26 Payload: api.Payload{
27 Type: "string",
28 Value: "default",
29 },
30 }
31
32 variant := unleash.GetVariant("checkout-experiment", unleash.VariantOptions{
33 VariantFallbackFunc: func(feature string, _ *unleashcontext.Context) *api.Variant {
34 if feature == "checkout-experiment" {
35 return fallbackVariant
36 }
37 return api.GetDefaultVariant()
38 },
39 })
40
41 if variant.Enabled {
42 // use variant.Name and variant.Payload
43 }
44}

The SDK can return two forms of the disabled variant:

  • Feature disabled or not found: { name: "disabled", enabled: false, featureEnabled: false }
  • Feature enabled but no variant resolved (for example no variants configured): { name: "disabled", enabled: false, featureEnabled: true }

In both cases, name is disabled. The enabled: false field marks this as the SDK’s default fallback variant, which disambiguates it from a real variant that happens to be named "disabled".

Unleash context

Use Unleash context to drive strategy and constraint evaluation for both IsEnabled and GetVariant.

1package main
2
3import (
4 "net/http"
5
6 unleash "github.com/Unleash/unleash-go-sdk/v6"
7 unleashcontext "github.com/Unleash/unleash-go-sdk/v6/context"
8)
9
10func main() {
11 err := unleash.Initialize(
12 unleash.WithAppName("my-go-service"),
13 unleash.WithUrl("https://<your-unleash-instance>/api/"),
14 unleash.WithCustomHeaders(http.Header{"Authorization": {"<your-backend-token>"}}),
15 )
16 if err != nil {
17 panic(err)
18 }
19 defer unleash.Close()
20
21 ctx := unleashcontext.Context{
22 UserId: "user-123",
23 SessionId: "session-abc",
24 Properties: map[string]string{
25 "plan": "enterprise",
26 },
27 }
28
29 enabled := unleash.IsEnabled("my-feature", unleash.FeatureOptions{Ctx: ctx})
30 variant := unleash.GetVariant("checkout-experiment", unleash.VariantOptions{Ctx: ctx})
31
32 _ = enabled
33 enabled := unleash.IsEnabled("my-feature", unleash.FeatureOptions{Ctx: ctx})
34 if enabled {
35 // New behaviour
36 }
37
38 variant := unleash.GetVariant("checkout-experiment", unleash.VariantOptions{Ctx: ctx})
39
40 if variant.flagEnabled {
41 // checking variant name to pick New behaviour
42 }
43}

For stable gradual rollout and stickiness, include at least userId or sessionId.

Bootstrap flag data

Bootstrapping lets the SDK start from predefined flag configuration instead of waiting for the first network response.

1file, err := os.Open("bootstrap.json")
2if err != nil {
3 panic(err)
4}
5defer file.Close()
6
7err = unleash.Initialize(
8 unleash.WithAppName("my-go-service"),
9 unleash.WithUrl("https://<your-unleash-instance>/api/"),
10 unleash.WithCustomHeaders(http.Header{"Authorization": {"<your-backend-token>"}}),
11 unleash.WithStorage(&unleash.BootstrapStorage{Reader: file}),
12)
13if err != nil {
14 panic(err)
15}

BootstrapStorage falls back to the default on-disk cache if bootstrap parsing fails.

Custom bootstrap

If you need more control over the bootstrap source, you can implement your own Storage and pass it with WithStorage(...).

1package main
2
3import (
4 "encoding/json"
5 "net/http"
6 "os"
7
8 unleash "github.com/Unleash/unleash-go-sdk/v6"
9 "github.com/Unleash/unleash-go-sdk/v6/api"
10)
11
12type CustomBootstrapStorage struct {
13 Backing unleash.DefaultStorage
14 BootstrapJSON []byte
15}
16
17func (s *CustomBootstrapStorage) Init(backupPath, appName string) {
18 s.Backing.Init(backupPath, appName)
19}
20
21func (s *CustomBootstrapStorage) Load() (*api.FeatureResponse, error) {
22 if len(s.BootstrapJSON) > 0 {
23 var state api.FeatureResponse
24 if err := json.Unmarshal(s.BootstrapJSON, &state); err == nil {
25 return &state, nil
26 }
27 }
28
29 // Fallback to persisted cache when bootstrap data is missing or invalid.
30 return s.Backing.Load()
31}
32
33func (s *CustomBootstrapStorage) Persist(state *api.FeatureResponse) error {
34 return s.Backing.Persist(state)
35}
36
37func main() {
38 bootstrapJSON, err := os.ReadFile("bootstrap.json")
39 if err != nil {
40 panic(err)
41 }
42
43 err = unleash.Initialize(
44 unleash.WithAppName("my-go-service"),
45 unleash.WithUrl("https://<your-unleash-instance>/api/"),
46 unleash.WithCustomHeaders(http.Header{"Authorization": {"<your-backend-token>"}}),
47 unleash.WithStorage(&CustomBootstrapStorage{BootstrapJSON: bootstrapJSON}),
48 )
49 if err != nil {
50 panic(err)
51 }
52 defer unleash.Close()
53}

Your bootstrap payload must match the /client/features response format

Local caching and offline behavior

The default storage persists the latest feature payload to {backupPath}/unleash-repo-schema-v1-{appName}.json

By default, backupPath is os.TempDir(). On startup, the SDK loads cached state first, then fetches fresh data from Unleash.

If Unleash is temporarily unreachable:

  • The SDK continues serving cached or bootstrapped values.
  • Polling continues on WithRefreshInterval.
  • Flag evaluations fall back to false (or configured fallbacks) when no data exists.

Events

Implement listener interfaces and pass them with WithListener(...).

1type MyListener struct{}
2
3func (l *MyListener) OnError(err error) {}
4func (l *MyListener) OnWarning(warning error) {}
5func (l *MyListener) OnReady() {}
6func (l *MyListener) OnUpdate() {}
7func (l *MyListener) OnCount(name string, enabled bool) {}
8func (l *MyListener) OnSent(payload unleash.MetricsData) {}
9func (l *MyListener) OnRegistered(payload unleash.ClientData) {}
10func (l *MyListener) OnImpression(event unleash.ImpressionEvent) {}
Event callbackDescription
OnReady()First successful flag sync completed.
OnUpdate()A later flag update was fetched.
OnError(error)SDK encountered an error.
OnWarning(error)SDK emitted a warning (for example, deprecated URL format).
OnCount(string, bool)A flag evaluation was counted for metrics.
OnSent(MetricsData)Metrics payload was sent to Unleash.
OnRegistered(ClientData)SDK client registration was sent to Unleash.
OnImpression(ImpressionEvent)A flag was evaluated with impression data enabled.

Use unleash.DebugListener{} during development for built-in logging of all event types.

Impact metrics

Impact metrics are lightweight, application-level time-series metrics stored and visualized directly inside Unleash. Use them to connect application data, such as request counts, error rates, or latency, to your feature flags and release plans.

The SDK automatically attaches appName, and environment labels to all impact metrics.

Initialize the SDK and publish impact metrics with package-level functions.

1package main
2
3import (
4 "net/http"
5
6 unleash "github.com/Unleash/unleash-go-sdk/v6"
7)
8
9func main() {
10 err := unleash.Initialize(
11 unleash.WithAppName("my-go-service"),
12 unleash.WithEnvironment("production"),
13 unleash.WithUrl("https://<your-unleash-instance>/api/"),
14 unleash.WithCustomHeaders(http.Header{"Authorization": {"<your-backend-token>"}}),
15 )
16 if err != nil {
17 panic(err)
18 }
19 defer unleash.Close()
20
21 unleash.DefineCounter("request_count", "Total number of HTTP requests")
22 unleash.IncrementCounter("request_count")
23
24 unleash.DefineGauge("active_users", "Current number of active users")
25 unleash.UpdateGauge("active_users", 42)
26
27 unleash.DefineHistogram(
28 "request_time_ms",
29 "Request processing time in milliseconds",
30 50, 100, 200, 500, 1000,
31 )
32 unleash.ObserveHistogram("request_time_ms", 125)
33}

Metric types

Use counters for cumulative values that only increase, such as the total number of requests or errors.

1unleash.DefineCounter("request_count", "Total number of HTTP requests")
2unleash.IncrementCounter("request_count")

Impact metrics are batched and sent on the same interval as standard SDK metrics (WithMetricsInterval).

Custom strategies

Register custom strategies with WithStrategies(...).

1package main
2
3import (
4 unleashcontext "github.com/Unleash/unleash-go-sdk/v6/context"
5)
6
7type EmailStrategy struct{}
8
9func (s EmailStrategy) Name() string {
10 return "ActiveForUserWithEmail"
11}
12func (s EmailStrategy) IsEnabled(params map[string]any, ctx *unleashcontext.Context) bool {
13 if ctx == nil || ctx.Properties == nil {
14 return false
15 }
16 return ctx.Properties["email"] == "user@example.com"
17}

Then pass it:

1unleash.WithStrategies(&EmailStrategy{})

Unit testing

For unit tests, use an in-memory bootstrap storage and disable metrics. This gives deterministic flag state without depending on a real Unleash instance. The Go SDK does not have a direct disableRefresh option, so tests typically set a long WithRefreshInterval(...).

The SDK exposes the NewClient method so you can create scoped instances for testing purposes, rather than the singleton instance created with Initialize.

1package mypkg_test
2
3import (
4 "testing"
5 "time"
6
7 unleash "github.com/Unleash/unleash-go-sdk/v6"
8 "github.com/Unleash/unleash-go-sdk/v6/api"
9)
10
11type testStorage map[string]api.Feature
12
13func (s testStorage) Init(string, string) {}
14
15func (s testStorage) Load() (*api.FeatureResponse, error) {
16 features := make([]api.Feature, 0, len(s))
17 for _, f := range s {
18 features = append(features, f)
19 }
20
21 return &api.FeatureResponse{
22 Features: features,
23 Segments: []api.Segment{},
24 }, nil
25}
26
27func (s testStorage) Persist(*api.FeatureResponse) error { return nil }
28
29func TestFlags(t *testing.T) {
30 client, err := unleash.NewClient(
31 unleash.WithAppName("test-app"),
32 unleash.WithUrl("http://127.0.0.1:4242/api/"),
33 unleash.WithDisableMetrics(true),
34 unleash.WithRefreshInterval(24*time.Hour),
35 unleash.WithStorage(testStorage{
36 "my-feature": {
37 Name: "my-feature",
38 Enabled: true,
39 Strategies: []api.Strategy{
40 {Name: "default"},
41 },
42 Variants: []api.VariantInternal{
43 {
44 Variant: api.Variant{
45 Name: "blue",
46 Enabled: true,
47 FeatureEnabled: true,
48 },
49 Weight: 1000,
50 },
51 },
52 },
53 }),
54 )
55 if err != nil {
56 t.Fatal(err)
57 }
58 defer client.Close()
59
60 if !client.IsEnabled("my-feature", unleash.FeatureOptions{}) {
61 t.Fatal("expected my-feature to be enabled")
62 }
63
64 variant := client.GetVariant("my-feature", unleash.VariantOptions{})
65 if variant.Name != "blue" {
66 t.Fatalf("expected variant blue, got %s", variant.Name)
67 }
68}

Troubleshooting

If WaitForReady() blocks indefinitely:

  • Verify WithUrl points to your Unleash API base URL (for example, https://<your-instance>/api/).
  • Verify WithCustomHeaders includes a valid backend API token in Authorization.
  • Confirm network connectivity from your service to the Unleash instance.
  • Attach unleash.DebugListener{} or your own OnError/OnWarning handlers to inspect failures.

If IsEnabled always returns false:

  • Confirm the flag exists (check for typos).
  • Confirm the flag is enabled in the target environment.
  • Verify your Unleash context fields match your rollout strategy and constraints.
  • Verify the SDK completed initial sync (OnReady fired, or WaitForReady() returned).