Help us shape the future of Go! Take the 2026 Go Developer Survey

flaggr

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 18, 2026 License: MIT Imports: 10 Imported by: 0

README

Flaggr Go SDK

Go Reference CI

A Go client for Flaggr, an OpenFeature-compatible feature flag platform.

The client evaluates flags through the Flaggr REST API. It has typed methods for boolean, string, number and object flags. It can also answer boolean and string flags in-process from a cache that you load.

Installation

go get github.com/flaggr-dev/flaggr-go@latest

The module needs Go 1.27 or later and uses only the standard library. The package name is flaggr.

Quick start

package main

import (
	"context"
	"log"
	"os"

	flaggr "github.com/flaggr-dev/flaggr-go"
)

func main() {
	client, err := flaggr.NewClient(
		flaggr.WithAPIKey(os.Getenv("FLAGGR_API_KEY")),       // project API token (fgr_...)
		flaggr.WithServiceID(os.Getenv("FLAGGR_SERVICE_ID")), // service ID from the Flaggr console
	)
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	enabled, err := client.Boolean(context.Background(), "checkout-v2", false, flaggr.EvalContext{
		TargetingKey: "user-123",
	})
	if err != nil {
		log.Printf("checkout-v2: using the default: %v", err)
	}
	log.Printf("checkout-v2 = %v", enabled)
}

Authentication

The client needs two values from your Flaggr project:

  • An API token. Use a project API token (fgr_...). A token with read permission can evaluate flags. Create one in the project settings of the Flaggr console and keep it out of source control. Personal access tokens (fgp_...) can't evaluate flags. See API Tokens.
  • A service ID. This is the ID of the service whose flags you evaluate. Copy it from the service's Connection tab in the console. The service's slug or display name won't work.

Every request sends the token in an Authorization: Bearer <token> header.

Configuration

Option Default Description
WithAPIKey(token) required Project API token
WithServiceID(id) required Service ID
WithEnvironment(env) "production" The environment to evaluate in, such as development or staging
WithAPIURL(url) "https://flaggr.dev" Base URL of the Flaggr API. https://api.flaggr.dev is the data plane (see Rate limits)
WithHTTPClient(hc) &http.Client{Timeout: 5 * time.Second} Your own *http.Client, for timeouts, proxies or a custom transport. nil keeps the default
WithLocalEvaluation(true) false Answers Boolean and String from the local cache (see Local evaluation)

The options remove surrounding whitespace from the API key, service ID, environment and URL, so a value read from an environment variable or a secrets file with a trailing newline still works. They also remove trailing slashes from the URL.

NewClient returns an error when the API key or the service ID is empty. Each flag is configured separately in each environment, so a key can return different values in development, staging and production. See Environments.

Evaluating flags

Each evaluation sends one POST /api/flags/evaluate request.

Method Returns
Boolean(ctx, key, defaultValue, evalCtx...) (bool, error)
String(ctx, key, defaultValue, evalCtx...) (string, error)
Number(ctx, key, defaultValue, evalCtx...) (float64, error)
Object(ctx, key, defaultValue, evalCtx...) (map[string]interface{}, error)
Resolve(ctx, key, defaultValue, evalCtx...) (*EvaluationDetail, error) with the value, reason, variant and any error code

The evaluation context is optional. If you pass more than one, only the first is used.

ctx := context.Background()
user := flaggr.EvalContext{
	TargetingKey: "user-456",
	Attributes: map[string]interface{}{
		"plan":    "enterprise",
		"country": "AU",
		"seats":   25,
	},
}

theme, err := client.String(ctx, "theme", "light", user)
limit, err := client.Number(ctx, "rate-limit", 100, user)
banner, err := client.Object(ctx, "banner-config", map[string]interface{}{"text": ""}, user)

detail, err := client.Resolve(ctx, "checkout-v2", false, user)
if err == nil {
	log.Printf("value=%v reason=%s variant=%s", detail.Value, detail.Reason, detail.Variant)
}

TargetingKey is usually the user ID. Overrides and percentage rollouts are based on it. Attributes hold the values that your targeting rules match on. Attribute values must be strings, numbers or booleans: https://flaggr.dev returns 400 Bad Request for nested objects, arrays and nil. See Targeting Rules.

Rate limits

Outside local evaluation, each call to Boolean, String, Number, Object or Resolve is one HTTPS request, and the client doesn't cache answers. The default base URL, https://flaggr.dev, allows 1,000 requests a minute from one IP address, 5,000 per token and 10,000 per service. Past that it answers 429 Too Many Requests, and the typed methods return your default with an error. See Rate Limiting.

In a busy service, evaluate each flag once per request or job and pass the value along. You can also send evaluations to the data plane with flaggr.WithAPIURL("https://api.flaggr.dev"), which the TypeScript SDK uses by default. The data plane doesn't assign experiments yet, so a flag with a running experiment can return a different variant there than on https://flaggr.dev.

Error handling

The evaluation methods don't panic. A request can fail because of a network error, a timeout or a status other than 200, such as 401, 403 or 429. When it does, the typed methods return your default value and a non-nil error, and Resolve returns a nil detail and the error. The error names the status and quotes up to 512 bytes of the response body. Log the error and use the default:

enabled, err := client.Boolean(ctx, "checkout-v2", false, user)
if err != nil {
	log.Printf("checkout-v2: using the default: %v", err)
}

In two cases the typed methods return your default value with a nil error:

  • The flag doesn't exist in that service and environment. Call Resolve to see the reason: NOT_FOUND from https://flaggr.dev.
  • The flag's value is of a different type from the method you called, such as Number on a string flag. One exception: Boolean reads the string "true" as true and any other string as false.

Flaggr gives the same 401 or 403 for a service ID that doesn't exist or that the token can't read. If you get one, check the service ID as well as the token.

Local evaluation

With WithLocalEvaluation(true), Boolean and String answer from an in-process cache without a network call. You fill the cache yourself because the client doesn't download flag definitions. Number, Object and Resolve still call the API.

client, err := flaggr.NewClient(
	flaggr.WithAPIKey(os.Getenv("FLAGGR_API_KEY")),
	flaggr.WithServiceID(os.Getenv("FLAGGR_SERVICE_ID")),
	flaggr.WithLocalEvaluation(true),
)
if err != nil {
	log.Fatal(err)
}

client.LoadFlags([]flaggr.CachedFlag{{
	Key:          "dark-mode",
	Enabled:      true,
	DefaultValue: false,
	Overrides: []flaggr.CachedOverride{
		{Identifiers: []string{"user-123"}, Value: true},
	},
}})

on, _ := client.Boolean(ctx, "dark-mode", false, flaggr.EvalContext{TargetingKey: "user-123"}) // true

Local evaluation works like this:

  • A key that isn't in the cache returns your default.
  • A disabled flag returns its DefaultValue.
  • An enabled flag returns the first override whose Identifiers include the TargetingKey. If no override matches, it returns the flag's DefaultValue.
  • Cached values of the wrong type are skipped.

Targeting rules and percentage rollouts are evaluated only by the API. SetFlag adds or replaces one flag, InvalidateFlag removes one, and LoadFlags replaces the whole cache. A Client is safe for concurrent use.

Error reporting

The errorreport package sends your program's errors and panics to Flaggr. The console's Issues page groups them into issues and shows which flag variants each issue happens under. It needs a project API token (fgr_...), because Flaggr refuses SDK keys for error reports.

import "github.com/flaggr-dev/flaggr-go/errorreport"

func main() {
	reporter := errorreport.New(errorreport.Options{
		Token:       os.Getenv("FLAGGR_ERRORS_TOKEN"), // the default when Token is empty
		Release:     "checkout-api@2.8.1",
		Environment: "production",
	})
	defer reporter.Close()   // sends what is queued
	defer reporter.Recover() // reports a panic, then re-panics

	http.Handle("/", reporter.Middleware(mux)) // reports handler panics with the method and path

	if err := placeOrder(ctx); err != nil {
		reporter.CaptureError(err,
			errorreport.WithTags(map[string]string{"step": "pay"}),
			errorreport.WithFlags(map[string]string{"coupon-engine": variant}),
			errorreport.WithUser(userID),
		)
	}
}

What the package does:

  • Grouping. Flaggr groups events by the innermost frames of your own code, without line numbers, so a deploy doesn't split an issue. Use WithFingerprint to choose the group yourself.
  • Flags. SetFlag records a flag's variant for every later event, and WithFlags sets the variants on one event.
  • Context. SetTag, SetUser and AddBreadcrumb add to the events that follow.
  • Sending. Events are sent in the background in batches. Messages and stacks are cut to 2,000 characters. At most 60 events are sent a minute.
  • Bad tokens. If Flaggr refuses the token, the reporter stops sending and tells you through Options.Logf.
  • Opting out. Options.Disabled turns the reporter into a no-op.
Tracing

Set TracesSampleRate and the middleware also records requests as spans for Flaggr's Performance page. That page shows throughput, p50/p95/p99 latency and failures per route, plus each trace's waterfall:

reporter := errorreport.New(errorreport.Options{Token: token, Release: "checkout-api@2.8.1", TracesSampleRate: 0.1})
mux.HandleFunc("GET /orders/{id}", getOrder)             // spans are named "GET /orders/{id}"
http.ListenAndServe(":8080", reporter.Middleware(mux))

func getOrder(w http.ResponseWriter, r *http.Request) {
	span, ctx := reporter.StartSpan(r.Context(), "load order", "db.query") // a child span
	defer span.End()
	client := &http.Client{Transport: reporter.Transport(nil)}           // sends traceparent downstream
	req, _ := http.NewRequestWithContext(ctx, "GET", pricingURL, nil)
	_, _ = client.Do(req)
}

How tracing behaves:

  • Propagation. The middleware continues an incoming W3C traceparent header. It follows a caller that sampled the trace; for a caller that didn't (such as a browser that only passes the ids along), it samples the trace at its own TracesSampleRate and keeps the trace id. Transport passes traceparent on to the services you call, and records each call as an http.client span.
  • Linking errors to traces. Errors captured with WithRequest(r) or WithContext(ctx) carry the trace id, so the issue links to its trace. This works even when the trace isn't sampled.
  • Joining the browser. An X-Request-Id request header is recorded on the span, and Flaggr uses it to find the page load that made the request.

OpenFeature

Flaggr also serves the OpenFeature Remote Evaluation Protocol (OFREP). To use the OpenFeature Go SDK instead of this client, use its OFREP provider:

go get github.com/open-feature/go-sdk github.com/open-feature/go-sdk-contrib/providers/ofrep
provider := ofrep.NewProvider("https://api.flaggr.dev/api",
	ofrep.WithBearerToken(os.Getenv("FLAGGR_API_KEY")),
	ofrep.WithHeader("X-Service-Id", os.Getenv("FLAGGR_SERVICE_ID")),
	ofrep.WithHeader("X-Environment", "production"),
)
if err := openfeature.SetProviderAndWait(provider); err != nil {
	log.Fatal(err)
}
client := openfeature.NewClient("my-service")

enabled, err := client.BooleanValue(ctx, "checkout-v2", false, openfeature.NewEvaluationContext("user-123", nil))

The provider adds /ofrep/v1/evaluate/flags/{key} to the base URL itself. Set the X-Environment header: OFREP evaluates in development by default, while this client defaults to production. https://api.flaggr.dev/api is the data plane, which doesn't assign experiments yet. To get the same experiment assignments as this client's default, use https://flaggr.dev/api as the base URL.

Documentation

Security

To report a vulnerability, see SECURITY.md.

License

MIT

Documentation

Overview

Package flaggr provides a Go client for the Flaggr feature flag platform (https://flaggr.dev).

It wraps the Flaggr REST API for flag evaluation with typed methods for boolean, string, number, and object flags.

Install it with:

go get github.com/flaggr-dev/flaggr-go@latest

Usage:

client, err := flaggr.NewClient(
    flaggr.WithAPIKey(os.Getenv("FLAGGR_API_KEY")),       // project API token (fgr_...)
    flaggr.WithServiceID(os.Getenv("FLAGGR_SERVICE_ID")), // the service's ID, not its slug
)
if err != nil {
    log.Fatal(err)
}
defer client.Close()

enabled, err := client.Boolean(ctx, "checkout-v2", false, flaggr.EvalContext{
    TargetingKey: "user-123",
})

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type CachedFlag

type CachedFlag struct {
	Key          string
	Enabled      bool
	DefaultValue interface{}
	Overrides    []CachedOverride
}

CachedFlag stores flag configuration for zero-network local in-process evaluation.

type CachedOverride

type CachedOverride struct {
	Identifiers []string
	Value       interface{}
}

CachedOverride stores user/identity overrides.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is a Flaggr feature flag evaluation client.

func NewClient

func NewClient(opts ...Option) (*Client, error)

NewClient creates a new Flaggr client with the given options.

Example
package main

import (
	"context"
	"fmt"
	"log"
	"os"

	flaggr "github.com/flaggr-dev/flaggr-go"
)

func main() {
	client, err := flaggr.NewClient(
		flaggr.WithAPIKey(os.Getenv("FLAGGR_API_KEY")),       // project API token (fgr_...)
		flaggr.WithServiceID(os.Getenv("FLAGGR_SERVICE_ID")), // the service's ID, not its slug
		flaggr.WithEnvironment("production"),
	)
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	enabled, err := client.Boolean(context.Background(), "checkout-v2", false, flaggr.EvalContext{
		TargetingKey: "user-123",
		Attributes:   map[string]interface{}{"plan": "enterprise", "country": "AU"},
	})
	if err != nil {
		log.Printf("checkout-v2: using the default: %v", err)
	}
	fmt.Println("checkout-v2:", enabled)
}

func (*Client) Boolean

func (c *Client) Boolean(ctx context.Context, flagKey string, defaultValue bool, evalCtx ...EvalContext) (bool, error)

Boolean evaluates a boolean flag.

func (*Client) Close

func (c *Client) Close()

Close releases resources held by the client.

func (*Client) InvalidateFlag

func (c *Client) InvalidateFlag(flagKey string)

InvalidateFlag removes a flag from the local in-process cache.

func (*Client) LoadFlags

func (c *Client) LoadFlags(flags []CachedFlag)

LoadFlags replaces all flags in the local in-process cache.

func (*Client) Number

func (c *Client) Number(ctx context.Context, flagKey string, defaultValue float64, evalCtx ...EvalContext) (float64, error)

Number evaluates a number flag.

func (*Client) Object

func (c *Client) Object(ctx context.Context, flagKey string, defaultValue map[string]interface{}, evalCtx ...EvalContext) (map[string]interface{}, error)

Object evaluates an object flag.

func (*Client) Resolve

func (c *Client) Resolve(ctx context.Context, flagKey string, defaultValue interface{}, evalCtx ...EvalContext) (*EvaluationDetail, error)

Resolve evaluates a flag and returns the full evaluation detail.

Example
package main

import (
	"context"
	"fmt"
	"log"
	"os"

	flaggr "github.com/flaggr-dev/flaggr-go"
)

func main() {
	client, err := flaggr.NewClient(
		flaggr.WithAPIKey(os.Getenv("FLAGGR_API_KEY")),
		flaggr.WithServiceID(os.Getenv("FLAGGR_SERVICE_ID")),
	)
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	detail, err := client.Resolve(context.Background(), "checkout-flow-version", "v1", flaggr.EvalContext{
		TargetingKey: "user-42",
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("value=%v reason=%s variant=%s\n", detail.Value, detail.Reason, detail.Variant)
}

func (*Client) SetFlag

func (c *Client) SetFlag(flag CachedFlag)

SetFlag inserts or updates a flag in the local in-process cache.

func (*Client) String

func (c *Client) String(ctx context.Context, flagKey string, defaultValue string, evalCtx ...EvalContext) (string, error)

String evaluates a string flag.

type EvalContext

type EvalContext struct {
	TargetingKey string
	Attributes   map[string]interface{}
}

EvalContext provides targeting attributes for flag evaluation.

type EvaluationDetail

type EvaluationDetail struct {
	Value        interface{} `json:"value"`
	Reason       string      `json:"reason"`
	Variant      string      `json:"variant"`
	ErrorCode    string      `json:"errorCode,omitempty"`
	ErrorMessage string      `json:"errorMessage,omitempty"`
}

EvaluationDetail contains the full result of a flag evaluation.

type Option

type Option func(*Client)

Option configures a Client.

func WithAPIKey

func WithAPIKey(key string) Option

WithAPIKey sets the API authentication token. Surrounding whitespace, such as a trailing newline, is removed.

func WithAPIURL

func WithAPIURL(url string) Option

WithAPIURL sets the Flaggr API URL. Surrounding whitespace and trailing slashes are removed.

func WithEnvironment

func WithEnvironment(env string) Option

WithEnvironment sets the target environment (default: "production"). Surrounding whitespace is removed.

func WithHTTPClient

func WithHTTPClient(hc *http.Client) Option

WithHTTPClient sets a custom HTTP client. A nil client keeps the default.

func WithLocalEvaluation

func WithLocalEvaluation(enabled bool) Option

WithLocalEvaluation enables zero-network in-process memory evaluation.

Example
package main

import (
	"context"
	"fmt"
	"log"

	flaggr "github.com/flaggr-dev/flaggr-go"
)

func main() {
	client, err := flaggr.NewClient(
		flaggr.WithAPIKey("fgr_example"),
		flaggr.WithServiceID("example-service-id"),
		flaggr.WithLocalEvaluation(true),
	)
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	client.LoadFlags([]flaggr.CachedFlag{{
		Key:          "dark-mode",
		Enabled:      true,
		DefaultValue: false,
		Overrides: []flaggr.CachedOverride{
			{Identifiers: []string{"user-123"}, Value: true},
		},
	}})

	ctx := context.Background()
	forUser, _ := client.Boolean(ctx, "dark-mode", false, flaggr.EvalContext{TargetingKey: "user-123"})
	forOthers, _ := client.Boolean(ctx, "dark-mode", false, flaggr.EvalContext{TargetingKey: "user-456"})
	missing, _ := client.Boolean(ctx, "not-cached", true)
	fmt.Println(forUser, forOthers, missing)
}
Output:
true false true

func WithServiceID

func WithServiceID(id string) Option

WithServiceID sets the service identifier for evaluations. Surrounding whitespace is removed.

Directories

Path Synopsis
Package errorreport sends a Go program's errors and panics to Flaggr, where they are grouped into issues alongside the flag variants that were active when they happened (the console's Issues page).
Package errorreport sends a Go program's errors and panics to Flaggr, where they are grouped into issues alongside the flag variants that were active when they happened (the console's Issues page).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL