GrowthBook Go SDK
Requirements
- Go version 1.21 or higher (tested with 1.21, 1.22, and 1.23)
Installation
go get github.com/growthbook/growthbook-golang
Quick Usage
import (
"context"
"log"
gb "github.com/growthbook/growthbook-golang"
)
// Create a new client instance with a client key and a data source that loads features
// in the background using an SSE stream. Pass the client's options to the NewClient function.
client, err := gb.NewClient(
context.Background(),
gb.WithClientKey("sdk-XXXX"),
gb.WithSseDataSource(),
)
defer client.Close()
if err != nil {
log.Fatal("Client initialization failed: ", err)
}
// The data source starts asynchronously. Use EnsureLoaded to wait until the client data
// is initialized for the first time.
if err := client.EnsureLoaded(context.Background()); err != nil {
log.Fatal("Data loading failed: ", err)
}
// Create a child client with specific attributes.
attrs := gb.Attributes{"id": 100, "user": "user1"}
child, err := client.WithAttributes(attrs)
if err != nil {
log.Fatal("Child client creation failed: ", err)
}
// Evaluate a text feature.
buttonColor := child.EvalFeature(context.Background(), "buy-button-color")
if buttonColor.Value == "blue" {
// Perform actions for blue button.
}
// Evaluate a boolean feature.
darkMode := child.EvalFeature(context.Background(), "dark-mode")
if darkMode.On {
// Enable dark mode.
}
Client
The client is the core component of the GrowthBook SDK. After installing and importing the SDK, create a single shared instance of growthbook.Client
using the growthbook.NewClient
function with a list of options. You can customize the client with options such as a custom logger, client key, decryption key, default attributes, or a feature list loaded from JSON. The client is thread-safe and can be safely used from multiple goroutines.
While you can evaluate features directly using the main client instance, it’s recommended to create child client instances that include session- or query-specific data. To create a child client with local attributes, call client.WithAttributes
:
attrs := gb.Attributes{"id": 100, "user": "Bob"}
child, err := client.WithAttributes(attrs)
You can then evaluate features using the child client:
res := child.EvalFeature(context.Background(), "main-button-color")
Additional options, such as WithLogger
, WithUrl
, and WithAttributesOverrides
, can also be used to customize child clients. Since child clients share data with the main client instance, they will automatically receive feature updates.
To stop background updates, call client.Close()
on the main client instance when it is no longer needed.
Loading Features and Experiments
For the GrowthBook SDK to function, it requires feature and experiment definitions from the GrowthBook API. There are several ways to provide this data to the SDK.
Built-in Fetching and Caching
If you pass WithClientKey
and one of the options WithSseDataSource
or WithPollDatasource(period)
when calling gb.NewClient
, it will load feature definitions from the GrowthBook site and update them periodically—either using SSE (server-sent events) or polling. You can also provide the WithApiHost
and WithDecryptionKey
options if needed.
The loading of features is an asynchronous process so that your app is not blocked while waiting, and it can continue its initialization. If you need to ensure that the definitions are loaded, use the client.EnsureLoaded
call. This will block until the loading process finishes and will return an error if any failures occur. EnsureLoaded
is thread-safe and can be called from multiple goroutines simultaneously. Both NewClient
and EnsureLoaded
respect the contexts passed to them.
The features cache is shared among all child client instances created via client.WithXXX
calls.
Custom Integration
Feature definitions are stored in the client's shared data. Normally, the data source will download them from the GrowthBook site, but you can provide an initial set during the NewClient
call using the WithFeatures
, WithJsonFeatures
, or WithEncryptedJsonFeature
options. It is also possible to update the shared feature definitions using the SetXXXFeatures
client methods.
If you have obtained a GrowthBook API response in JSON, you can update the client's state with the client.UpdateFromApiResponseJSON
call.
Attributes
You can specify attributes about the current user and request. These attributes are used for the following purposes:
- Feature targeting (for example, paid users receive one value, while free users receive another).
- Assigning persistent variations in A/B tests (for example, a user with id "123" always gets variation B).
Attributes can be any JSON data type—boolean, float, string, array, or object—and are represented by the Attributes
type, which is an alias for Go’s generic map[string]interface{}
type used for JSON objects. If you know the attributes upfront, you can pass them into NewClient
using the WithAttributes
option:
attrs := gb.Attributes{
"id": "123",
"loggedIn": true,
"deviceId": "abc123def456",
"company": "acme",
"paid": false,
"url": "/pricing",
"browser": "chrome",
"mobile": false,
"country": "US",
}
client, err := gb.NewClient(context.Background(),
gb.WithAttributes(attrs),
)
You can also create a child client instance with updated attributes using the WithAttributes
method:
attrs := gb.Attributes{
"id": "100",
"mobile": true,
}
client, err := client.WithAttributes(attrs)
This will completely overwrite the existing attributes object with the values you provide. If you want to merge the new attributes with the existing ones instead, you can use the WithAttributeOverrides
method:
client, err := client.WithAttributeOverrides(attrs)
This method updates only the fields provided in attrs
, keeping the other fields from the original client instance.
Be aware that changing attributes may change the assigned feature values. This can be disorienting to users if not handled carefully. A common approach is to refresh attributes only on navigation, when the window is focused, or after a user performs a major action such as logging in.
Tracking Callback
Whenever an experiment is run to determine the value of a feature, you can run a callback function to record the assigned value in your event tracking or analytics system.
You can set up two callbacks to track experiment results and feature usage:
- ExperimentCallback: Triggered when a user is included in an experiment.
- FeatureUsageCallback: Triggered on each feature evaluation.
Additionally, you can attach extra data that will be sent with each callback. These callbacks can be set globally via the NewClient
function using the WithExperimentCallback
and WithFeatureUsageCallback
options. Alternatively, you can set them locally when creating child clients using similar methods (for example, client.WithExperimentCallback
). Extra data is set via the WithExtraData
option.
cb := func(ctx context.Context, exp *Experiment, result *ExperimentResult, extra any) {
user, ok := extra.(User)
if !ok {
return
}
// Example using Segment.io
segment.Enqueue(analytics.Track{
UserId: user.id,
Event: "Experiment Viewed",
Properties: analytics.NewProperties().
Set("experimentId", exp.Key).
Set("variationId", result.VariationID),
})
}
client := gb.NewClient(ctx, gb.WithExperimentCallback(cb), /* other options */)
Feature Usage Callback
GrowthBook can fire a callback whenever a feature is evaluated for a user. This is useful for updating third-party tools such as New Relic or DataDog.
Like the tracking callback, this can be defined on either the global GrowthBook client instance or as part of a child client. It also receives the context and any extra data as parameters.
cb := func(ctx context.Context, featureKey string, result *FeatureResult, extra any) {
user, ok := extra.(User)
if !ok {
return
}
log.Println("Feature:", featureKey, "Result:", result.Value, "user_id:", user.id)
}
client := gb.NewClient(ctx, gb.WithFeatureUsageCallback(cb), /* other options */)
The result
argument is the same as what is returned from EvalFeature
.
Using Features
The primary method, client.EvalFeature(ctx, key)
, accepts a feature key and uses the stored feature definitions and attributes to evaluate the feature value. It returns a FeatureResult
value that includes detailed information about why the value was assigned to the user:
Value
: The JSON value of the feature (ornil
if not defined), represented as aFeatureValue
(an alias forinterface{}
, using Go's default behavior for JSON).On
andOff
: The JSON value cast as booleans (to make your code easier to read).Source
: A value of typeFeatureResultSource
that explains why the value was assigned to the user. Possible values includeUnknownFeatureResultSource
,DefaultValueResultSource
,ForceResultSource
, orExperimentResultSource
.Experiment
: Information about the experiment (if any) used to assign the value.ExperimentResult
: The result of the experiment (if any) that determined the value.
Here's an example that uses all of these fields:
result, err := client.EvalFeature(context.TODO(), "my-feature")
if err != nil {
// Handle the error
}
// The JSON value (which may be null, a string, boolean, number, array, or object).
fmt.Println(result.Value)
if result.On {
// The feature value is truthy (in a JavaScript sense).
}
if result.Off {
// The feature value is falsy.
}
// If the feature value was assigned as part of an experiment:
if result.Source == gb.ExperimentResultSource {
// Get all the possible variations that could have been assigned.
fmt.Println(result.Experiment.Variations)
}
Inline Experiments
Experiments can be defined and run using the Experiment
type and the RunExperiment
method of the client. Experiment definitions can be created directly as values of the Experiment
type, or parsed from JSON using Go’s json.Unmarshal
function. Passing an Experiment
value to the RunExperiment
method will run the experiment and return an ExperimentResult
that contains the resulting feature value. This approach allows users to run arbitrary experiments without providing feature definitions upfront.
experiment := &gb.Experiment{
Key: "my-experiment",
Variations: []gb.FeatureValue{"red", "blue", "green"},
}
result := client.RunExperiment(context.TODO(), experiment)
A full list of experiment fields can be found in the documentation .
Inline Experiment Return Value
A call to RunExperiment
returns a value of type *ExperimentResult
:
result := client.RunExperiment(context.TODO(), experiment)
// Whether the user is part of the experiment.
fmt.Println(result.InExperiment) // true or false
// The index of the assigned variation.
fmt.Println(result.VariationId) // 0 or 1
// The value of the assigned variation.
fmt.Println(result.Value) // "A" or "B"
// The user attribute used to assign a variation.
fmt.Println(result.HashAttribute) // "id"
// The value of that attribute.
fmt.Println(result.HashValue) // e.g., "123"
The InExperiment
flag is set to true only if the user was randomly assigned a variation. If the user fails any targeting rules or is forced into a specific variation, this flag will be false.
Logging
The SDK uses the slog
logger instance. You can set up your own logger using the WithLogger
option when calling the NewClient
function. It is also possible to create a child GrowthBook client with its own logger, parameterized with additional data, as shown below:
func main() {
client, err := gb.NewClient(/* options */)
if err != nil {
log.Fatal(err)
}
// ...
}
func handleRequest(ctx context.Context, client *gb.Client, r request) {
traceId := ctx.Value("traceId")
logger := slog.Default().With("traceId", traceId)
client, err := client.WithLogger(logger)
if err != nil {
log.Fatal(err)
}
client, err = client.WithAttributes(gb.Attributes{"user_id": r.user_id})
if err != nil {
log.Fatal(err)
}
// Now, all calls to the client will use the local logger and attributes.
}
Further Reading
This version addresses grammatical issues and clarifies the text while maintaining the original content and code examples.