Go SDK
Go feature flags, evaluated in-process
One client, held for the life of the process, evaluating flags from a ruleset streamed into memory. Checks are a map lookup and a hash, so a flag read is safe inside a hot handler, a worker loop, or a goroutine fan-out.
01 · Drop it in
The planned Go API, exactly as it will ship
The SDK is part of the early-access program, and this is its real surface: one install, one client, one call per decision. Every toggle, rollout step and kill-switch flip you make in the console reaches this code in about 3 seconds, with no redeploy.
Zero third-party dependencies; the client is a single struct you can inject and fake in tests.
package main
import (
"context"
"net/http"
"os"
"github.com/toggled/toggled-go"
)
var flags = toggled.New(os.Getenv("TOGGLED_SDK_KEY"))
func checkout(w http.ResponseWriter, r *http.Request) {
user := userFrom(r.Context())
if flags.Enabled("checkout.new-flow", toggled.User{
ID: user.ID,
Plan: user.Plan,
}, false) {
newCheckout(w, r)
return
}
classicCheckout(w, r)
}
02 · Built for Go
What Go feature flags get you
Safe in the hot path
Enabled takes the fallback as its last argument and never makes a network call, so you can read a flag per request, per iteration, or inside a goroutine without thinking about latency.
Concurrency-safe by design
One client per process, shared across every goroutine. The ruleset is swapped atomically when it updates, so there is no lock contention and no torn read mid-request.
Context-aware kill switches
Flip a flag off and every replica converges in about 3 seconds. No rolling restart, no redeploy, and the OFF path is the code that worked yesterday.
03 · Then the usual lifecycle
Flag it, target it, roll it out, measure, kill
Once checkout.new-flow exists in your Go code, everything else happens in the console: target the internal team, run a gradual rollout from 1% to 100%, read the impact with built-in A/B testing, and keep the kill switch under your thumb. The full walkthrough is on how Toggled works.
Flat pricing applies here too: unlimited seats and unlimited flags on every plan, so the whole Go team gets an account. Numbers are on the pricing page.
Same flags, other stacks
Cohorts agree across SDKs: a user in the 10% on your Go side is in the same 10% everywhere else.
Java, Rails, iOS and Android SDKs share the same core and ship with early access.
04 · Where the check goes
Where a flag check belongs in a Go codebase
01
On the handler struct, not in a global
Hang the client on the struct that carries your handler dependencies and pass it in from main. A package-level client is convenient for a week and untestable forever, and it quietly invites the worst mistake in this list: caching the value instead of holding the client.
02
Behind your own narrow interface
Declare the single method you use in the package that consumes it and let the SDK client satisfy it implicitly. Your handlers then depend on a two-line interface rather than on the SDK, and the fake in your test is a map type with one method on it.
03
In middleware for endpoint-level gates
A beta endpoint or a new API version is an http.Handler wrapper that reads the flag and returns 404 when it is off. The handler underneath stays completely flag-free, and retiring the gate is deleting one wrapper from your router setup.
05 · Gotchas
Three ways Go feature flags go wrong
Gotcha 01
Caching the value in a package-level var
Reading the flag once at startup into a package-level bool gives you a value frozen at boot: the kill switch flips, the ruleset updates, and your binary keeps serving the old path until someone restarts it. Read the flag at the point where the decision is made, per request, and let the in-memory ruleset make that read free.
Gotcha 02
A flag read that outlives the request context
If the client can block, which it can during its very first fetch, a read at the top of a handler may sit there past the request deadline and hold the goroutine. Initialize the client and wait for readiness in main before you serve traffic, then keep the per-request call non-blocking with an explicit fallback.
Gotcha 03
Losing the user key in a goroutine fan-out
Spawning goroutines that each evaluate the flag with a zero-value user puts them in a different rollout bucket than the request that spawned them, so half the work in one request takes the new path and half the old one. Evaluate once in the parent and pass the resulting bool into the goroutine.
06 · Testing
Testing both paths in Go
Declare the flag client as a one-method interface in your own package, then satisfy it with a fake backed by a map[string]bool. A table test lists the cases, flag on, flag off, and client unavailable, and runs the same handler against each without a network call. The fake belongs in the package under test, which means your test file never imports the SDK at all.
type flagClient interface {
Enabled(key string, user toggled.User, fallback bool) bool
}
type fakeFlags map[string]bool
func (f fakeFlags) Enabled(key string, _ toggled.User, fallback bool) bool {
v, ok := f[key]
if !ok {
return fallback
}
return v
}
func TestCheckout(t *testing.T) {
cases := []struct {
name string
on bool
want string
}{
{"new flow", true, "new"},
{"classic flow", false, "classic"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
h := &handler{flags: fakeFlags{"checkout.new-flow": tc.on}}
rec := httptest.NewRecorder()
h.checkout(rec, httptest.NewRequest("POST", "/checkout", nil))
if got := rec.Body.String(); got != tc.want {
t.Fatalf("got %q, want %q", got, tc.want)
}
})
}
}
07 · People also ask
Go feature flag questions, answered
How do you use feature flags in Go?
Create one client at startup, store it on the struct that holds your handler dependencies, and call Enabled with the user context and a fallback inside the handler. The client evaluates from an in-memory ruleset, so the call is a map lookup and a hash, which is safe to make on every single request.
Are feature flag checks goroutine-safe in Go?
They should be, and a well built client makes them so by swapping the ruleset atomically rather than mutating it in place. Concurrent Enabled calls from thousands of goroutines then read a consistent snapshot with no lock contention and no torn read. What is not safe is a package-level variable a background goroutine writes to.
Is it safe to read a feature flag in a hot path?
Yes, when evaluation is local. The cost is a map lookup plus a hash of the user key, on the order of tens of nanoseconds, with no allocation on the happy path. The thing to avoid is an SDK that calls an HTTP API per evaluation, which adds a round trip and a new failure mode to every request.
How do you inject a feature flag client for testing in Go?
Define a small interface in the consuming package, Enabled(key string, user User, fallback bool) bool, accept it on your handler struct, and pass the real client in main and a fake in tests. Implicit interface satisfaction means your test file never imports the SDK, and the fake is roughly six lines.
More answers, about the product rather than the code, live on the FAQ.
Early access · launching soon
Put your first Go feature behind a flag
Join the early-access list and be first in line when spots open. Flat pricing, unlimited seats, no card required.
No card required · your data stays yours
Keep exploring
More feature flag resources
- Feature Flags Platform
- Managing Feature Flags
- Feature Flag Tools
- A/B Testing Software Powered by Feature Flags
- Feature Flag Service Use Cases for Product Teams
- Feature Flag Software Pricing, Unlimited Seats
- Feature Flags Explained
- LaunchDarkly Alternative With Flat Pricing
- LaunchDarkly Pricing
- Best Feature Flag Tools in 2026, Compared
- GitLab Feature Flags
- PostHog Feature Flags