OpenFeature-compliant provider SDK for Cloudflare Flagship in Go server applications.
The Go SDK supports HTTP mode only. The Cloudflare Workers binding mode is exclusive to the TypeScript SDK.
go get github.com/cloudflare/flagship/sdks/gopackage main
import (
"context"
"log"
"time"
flagship "github.com/cloudflare/flagship/sdks/go"
"github.com/open-feature/go-sdk/openfeature"
)
func main() {
ctx := context.Background()
provider, err := flagship.NewProvider(flagship.Options{
AppID: "your-app-id",
AccountID: "your-account-id",
AuthToken: "your-token",
CacheTTL: 30 * time.Second, // cache evaluations per context for 30s (off by default)
})
if err != nil {
log.Fatal(err)
}
if err := openfeature.SetProviderAndWait(provider); err != nil {
log.Fatal(err)
}
defer openfeature.Shutdown()
client := openfeature.NewDefaultClient()
evalCtx := openfeature.NewEvaluationContext("user-123", map[string]any{
"plan": "premium",
})
enabled, err := client.BooleanValue(ctx, "dark-mode", false, evalCtx)
if err != nil {
log.Fatal(err)
}
log.Println("dark-mode:", enabled)
}flagship.NewProvider and flagship.NewClient accept either AppID plus AccountID or a full Endpoint URL.
provider, err := flagship.NewProvider(flagship.Options{
AppID: "your-app-id",
AccountID: "your-account-id",
// Endpoint: "http://localhost:8787/client/v4/accounts/acct/flagship/apps/app/evaluate",
// BaseURL: "http://localhost:8787",
AuthToken: "your-token",
Headers: http.Header{
"X-Custom": []string{"value"},
},
HeadersFactory: func(ctx context.Context) (http.Header, error) {
return http.Header{"Authorization": []string{"Bearer rotated-token"}}, nil
},
Timeout: 5 * time.Second,
Retries: 1,
RetryDelay: time.Second,
CacheTTL: 30 * time.Second,
CacheMaxSize: 1000,
Logging: true,
})| Option | Description |
|---|---|
AppID |
Flagship app ID. Mutually exclusive with Endpoint. |
AccountID |
Required with AppID. |
BaseURL |
Base URL override used with AppID; defaults to https://api.cloudflare.com. |
Endpoint |
Full absolute evaluation endpoint URL. |
AuthToken |
Adds Authorization: Bearer <token> to each request. |
Headers |
Static headers. Explicit Authorization overrides AuthToken. |
HeadersFactory |
Dynamic per-request headers. Values override Headers and AuthToken. |
HTTPClient |
Custom HTTP client. |
Timeout |
Per-attempt timeout; defaults to 5 seconds. |
Retries |
Retry attempts on transient errors; defaults to 1 and is capped at 10. |
DisableRetries |
Disables retries when set to true. |
RetryDelay |
Delay between retries; defaults to 1 second and is capped at 30 seconds. |
CacheTTL |
Cache TTL; enables response caching when greater than 0. |
CacheMaxSize |
Maximum cached entries; defaults to 1000 when CacheTTL is set. |
LocalEvaluation |
Downloads definitions once and evaluates flags in-process; off by default. |
RefreshInterval |
Background definitions refresh period in local mode; defaults to 30s. |
Logging |
Enables provider debug/error logs; off by default. |
Logger |
Optional slog-compatible logger. |
Hooks |
Provider-level OpenFeature hooks. |
When LocalEvaluation is enabled, the provider downloads the app's flag definitions once during Init / SetProviderAndWait, evaluates flags in-process with no network call per evaluation, and refreshes definitions in the background on RefreshInterval (default 30s).
provider, err := flagship.NewProvider(flagship.Options{
AppID: "your-app-id",
AccountID: "your-account-id", // required — used as the rollout hash seed
AuthToken: "your-read-token", // needs app **read** permission (not evaluate)
LocalEvaluation: true,
RefreshInterval: 30 * time.Second,
})Requirements and constraints:
AccountIDis always required in local mode (including whenEndpointis used).- The auth token needs app read permission, not
evaluate. Endpoint, when set, must end in/evaluateso the definitions URL can be derived.- Incompatible with
CacheTTL— there is nothing to cache per evaluation. - Local evaluations do not appear in server-side analytics.
- Init fails (and puts the OpenFeature provider in
ERROR) if the first definitions fetch fails. Later refresh failures keep the last good snapshot.
See examples/local for a full program.
The provider can cache evaluations to avoid a network round-trip for repeated flag/context pairs. Caching is off by default and enabled by setting CacheTTL:
provider, err := flagship.NewProvider(flagship.Options{
AppID: "your-app-id",
AccountID: "your-account-id",
AuthToken: "your-token",
CacheTTL: 30 * time.Second, // values may be up to this stale
CacheMaxSize: 1000, // LRU-evicted beyond this many entries
})Each entry is keyed by flag key, flag type, and the full evaluation context, so distinct contexts never share a value. Cache hits resolve with reason == openfeature.CachedReason. Disabled flags, errors, and type mismatches are never cached. Because freshness is TTL-based, a flag change in Flagship takes effect after the entry expires.
The cache is per-provider instance, guarded by a mutex for concurrent use, and cleared on Shutdown.
Primitive-only context is sent as URL query parameters. Context containing maps, slices, arrays, or nil uses a JSON POST request. Nested values are preserved recursively, and time.Time values are converted to RFC3339Nano strings. Unsupported map keys, structs, cyclic values, and non-finite numbers return INVALID_CONTEXT before transport.
All OpenFeature server-side flag types are supported:
enabled, _ := client.BooleanValue(ctx, "new-checkout", false, evalCtx)
variant, _ := client.StringValue(ctx, "homepage-hero", "control", evalCtx)
rate, _ := client.FloatValue(ctx, "sample-rate", 0.1, evalCtx)
limit, _ := client.IntValue(ctx, "upload-limit", 10, evalCtx)
config, _ := client.ObjectValue(ctx, "ui-config", map[string]any{"theme": "light"}, evalCtx)Use *ValueDetails methods when you need reason, variant, metadata, or error codes.
Provider resolution methods return the default value plus an OpenFeature resolution error when evaluation fails. The lower-level FlagshipClient returns *flagship.Error with a typed Code, HTTP StatusCode, and wrapped cause when available.
| Error code | Cause |
|---|---|
FLAG_NOT_FOUND |
Flag key does not exist (HTTP 404 or missing from local definitions) |
BAD_REQUEST |
Evaluation request was invalid (HTTP 400) |
INVALID_CONTEXT |
Evaluation context contains unsupported value types |
NETWORK_ERROR |
Network request failed |
TIMEOUT_ERROR |
Request timed out |
PARSE_ERROR |
API response or local flag definition was invalid |
PROVIDER_NOT_READY |
Local evaluation used before init or after shutdown |
GENERAL |
Any other transient or unexpected failure |
400 and 404 responses are never retried. Other failures are retried up to Retries times unless DisableRetries is set.
gofmt -w .
go vet ./...
go test ./...