# GO Feature Flag
GO Feature Flag is a lightweight, self-hosted, and open-source feature flag solution that provides a simple and complete feature flag implementation. It supports the OpenFeature standard, enabling multi-language support through a relay proxy API server. The solution allows teams to store configuration flags in various formats (YAML, JSON, TOML) and locations (GitHub, S3, HTTP endpoints, Kubernetes ConfigMaps, MongoDB, Redis, etc.), while providing complex targeting rules, percentage-based rollouts, progressive deployments, scheduled changes, and A/B testing experimentation.
GO Feature Flag can be used in two main modes: as a GO module directly embedded in Go applications for local flag evaluation, or via the relay proxy which exposes a REST API compatible with OpenFeature SDKs in multiple languages including JavaScript/TypeScript, Java/Kotlin, Python, .NET, Ruby, PHP, Swift, and Android. The relay proxy supports both remote evaluation (API calls per evaluation) and in-process evaluation (local caching with periodic sync), making it suitable for various performance requirements and deployment scenarios.
## Feature Flag Configuration
Feature flags are defined in YAML, JSON, or TOML files with variations, targeting rules, and default rules for determining which value to serve to users.
```yaml
# flag-config.goff.yaml
# Boolean flag with percentage-based rollout
new-checkout-flow:
variations:
enabled: true
disabled: false
defaultRule:
percentage:
enabled: 20
disabled: 80
# Multi-variant string flag with targeting rules
scream-level-feature:
variations:
low: "whisper"
medium: "talk"
high: "scream"
targeting:
- name: admin-users
query: admin eq true
variation: high
- name: beta-testers
query: beta eq true
percentage:
medium: 50
high: 50
defaultRule:
variation: low
# JSON object flag for configuration
user-dashboard-config:
variations:
v1:
layout: "classic"
showAnalytics: false
maxWidgets: 5
v2:
layout: "modern"
showAnalytics: true
maxWidgets: 10
targeting:
- query: plan eq "enterprise"
variation: v2
defaultRule:
variation: v1
metadata:
description: "Dashboard configuration by plan type"
owner: "platform-team"
```
## Relay Proxy Configuration
The relay proxy is configured via a YAML file that specifies where to retrieve flag configurations, how to export evaluation data, and server settings.
```yaml
# goff-proxy.yaml
server:
mode: http
port: 1031
host: 0.0.0.0
# Where to retrieve flag configurations
retrievers:
- kind: file
path: /goff/flags.yaml
- kind: s3
bucket: my-feature-flags-bucket
item: flags/production.yaml
# Polling interval in milliseconds
pollingInterval: 60000
# Start even if retriever fails initially
startWithRetrieverError: false
# Export evaluation data
exporters:
- kind: s3
bucket: evaluation-data-bucket
flushInterval: 10000
- kind: log
# Notify on flag changes
notifiers:
- kind: slack
webhookUrl: "https://hooks.slack.com/services/xxx/yyy/zzz"
- kind: webhook
endpointUrl: "https://my-app.com/webhook/flags-changed"
# API key authentication
authorizedKeys:
evaluation:
- "evaluation-api-key-1"
- "evaluation-api-key-2"
admin:
- "admin-api-key"
# Add context to all evaluations
evaluationContextEnrichment:
env: production
version: "1.0.0"
# Enable Swagger UI at /swagger/
swagger:
enabled: true
host: localhost:1031
# Logging configuration
logLevel: INFO
logFormat: json
```
## Docker Deployment
Deploy the relay proxy using Docker with mounted configuration files.
```bash
# Basic deployment with local flag file
docker run -d \
--name go-feature-flag \
-p 1031:1031 \
-v $(pwd)/flag-config.yaml:/goff/flags.yaml \
-v $(pwd)/goff-proxy.yaml:/goff/goff-proxy.yaml \
gofeatureflag/go-feature-flag:latest
# With environment variable overrides
docker run -d \
--name go-feature-flag \
-p 1031:1031 \
-e LOGLEVEL=debug \
-e POLLINGINTERVAL=30000 \
-e RETRIEVERS_0_KIND=github \
-e RETRIEVERS_0_REPOSITORYSLUG=my-org/feature-flags \
-e RETRIEVERS_0_PATH=flags/production.yaml \
-e RETRIEVERS_0_BRANCH=main \
-e RETRIEVERS_0_TOKEN=ghp_xxxxxxxxxxxx \
gofeatureflag/go-feature-flag:latest
# Health check
curl http://localhost:1031/health
# Response: {"initialized":true}
# Get proxy info
curl http://localhost:1031/info
# Response: {"cacheRefresh":"2024-01-15T10:30:00Z"}
```
## GO Module Usage
Use GO Feature Flag directly as a Go module for local flag evaluation without the relay proxy.
```go
package main
import (
"context"
"fmt"
"log"
"time"
ffclient "github.com/thomaspoignant/go-feature-flag"
"github.com/thomaspoignant/go-feature-flag/ffcontext"
"github.com/thomaspoignant/go-feature-flag/retriever/fileretriever"
"github.com/thomaspoignant/go-feature-flag/retriever/httpretriever"
)
func main() {
// Initialize with local file retriever
err := ffclient.Init(ffclient.Config{
PollingInterval: 60 * time.Second,
Retriever: &fileretriever.Retriever{
Path: "flag-config.goff.yaml",
},
// Or use HTTP retriever for remote files
// Retriever: &httpretriever.Retriever{
// URL: "https://my-flags-server.com/flags.yaml",
// Timeout: 10 * time.Second,
// },
Environment: "production",
})
if err != nil {
log.Fatal(err)
}
defer ffclient.Close()
// Create evaluation context with user attributes
user := ffcontext.NewEvaluationContextBuilder("user-12345").
AddCustom("email", "john@example.com").
AddCustom("firstname", "John").
AddCustom("lastname", "Doe").
AddCustom("admin", true).
AddCustom("plan", "enterprise").
AddCustom("anonymous", false).
Build()
// Boolean flag evaluation
showNewFeature, _ := ffclient.BoolVariation("new-checkout-flow", user, false)
if showNewFeature {
fmt.Println("Showing new checkout flow")
}
// String flag evaluation
screamLevel, _ := ffclient.StringVariation("scream-level-feature", user, "low")
fmt.Printf("Scream level: %s\n", screamLevel)
// JSON object flag evaluation
dashboardConfig, _ := ffclient.JSONObjectVariation("user-dashboard-config", user, map[string]interface{}{})
fmt.Printf("Dashboard config: %+v\n", dashboardConfig)
// Get detailed evaluation result
details, _ := ffclient.BoolVariationDetails("new-checkout-flow", user, false)
fmt.Printf("Flag value: %v, Reason: %s, Variant: %s\n",
details.Value, details.Reason, details.Variant)
}
```
## Node.js/TypeScript SDK
Use the OpenFeature SDK with the GO Feature Flag provider for server-side JavaScript/TypeScript applications.
```typescript
import { OpenFeature, EvaluationContext } from "@openfeature/server-sdk";
import { GoFeatureFlagProvider, EvaluationType } from "@openfeature/go-feature-flag-provider";
// Initialize with remote evaluation (API call per evaluation)
const remoteProvider = new GoFeatureFlagProvider({
endpoint: "http://localhost:1031/",
evaluationType: EvaluationType.Remote,
apiKey: "my-evaluation-api-key",
timeout: 10000,
});
// Or initialize with in-process evaluation (local caching)
const inProcessProvider = new GoFeatureFlagProvider({
endpoint: "http://localhost:1031/",
evaluationType: EvaluationType.InProcess,
flagChangePollingIntervalMs: 30000,
dataFlushInterval: 60000,
maxPendingEvents: 10000,
disableDataCollection: false,
});
// Set the provider
await OpenFeature.setProviderAndWait(remoteProvider);
const client = OpenFeature.getClient("my-app");
// Create evaluation context (targetingKey is mandatory)
const userContext: EvaluationContext = {
targetingKey: "user-12345",
email: "john@example.com",
firstname: "John",
lastname: "Doe",
admin: true,
plan: "enterprise",
company_info: { name: "Acme Corp", size: 500 },
labels: ["beta", "premium"],
};
// Boolean flag evaluation
const showNewFeature = await client.getBooleanValue("new-checkout-flow", false, userContext);
console.log(`Show new feature: ${showNewFeature}`);
// String flag evaluation with details
const screamDetails = await client.getStringDetails("scream-level-feature", "low", userContext);
console.log(`Scream level: ${screamDetails.value}, reason: ${screamDetails.reason}`);
// Number flag evaluation
const discountPercentage = await client.getNumberValue("discount-percentage", 0, userContext);
console.log(`Discount: ${discountPercentage}%`);
// Object flag evaluation
const dashboardConfig = await client.getObjectValue("user-dashboard-config", {}, userContext);
console.log(`Dashboard config:`, dashboardConfig);
// Track custom events
client.track("purchase_completed", userContext, {
amount: 99.99,
currency: "USD",
items: 3,
});
```
## React SDK
Use the OpenFeature React SDK with GO Feature Flag for client-side React applications.
```tsx
import React from "react";
import { OpenFeature, OpenFeatureProvider, useFlag, useSuspenseFlag } from "@openfeature/react-sdk";
import { GoFeatureFlagWebProvider } from "@openfeature/go-feature-flag-web-provider";
// Initialize the provider
const goFeatureFlagProvider = new GoFeatureFlagWebProvider({
endpoint: "http://localhost:1031",
apiKey: "my-web-api-key",
pollInterval: 30000,
});
// Set initial evaluation context
OpenFeature.setContext({
targetingKey: "user-12345",
email: "john@example.com",
admin: false,
plan: "free",
});
// Set the provider (no need to await - React SDK handles suspense)
OpenFeature.setProvider(goFeatureFlagProvider);
// Main App component with provider wrapper
function App() {
return (
}>
);
}
// Component using feature flags
function Dashboard() {
// Boolean flag with useFlag hook
const { value: showNewDashboard, reason } = useFlag("new-dashboard", false);
// String flag
const { value: theme } = useFlag("ui-theme", "light");
// Object flag for configuration
const { value: config } = useFlag("dashboard-config", { widgets: [] });
return (
{showNewDashboard ? (
) : (
)}
Flag reason: {reason}
);
}
// Update context when user changes
function UserProfile({ user }) {
React.useEffect(() => {
OpenFeature.setContext({
targetingKey: user.id,
email: user.email,
admin: user.isAdmin,
plan: user.subscription,
});
}, [user]);
return ;
}
function LoadingSpinner() {
return Loading feature flags...
;
}
export default App;
```
## Java/Kotlin SDK
Use the OpenFeature Java SDK with GO Feature Flag provider for JVM applications.
```java
import dev.openfeature.sdk.*;
import dev.openfeature.contrib.providers.gofeatureflag.*;
import java.util.Map;
public class FeatureFlagExample {
public static void main(String[] args) {
// Initialize provider with in-process evaluation
GoFeatureFlagProviderOptions options = GoFeatureFlagProviderOptions.builder()
.endpoint("https://relay-proxy.example.com")
.evaluationType(EvaluationType.IN_PROCESS)
.apiKey("my-api-key")
.timeout(10000)
.flagChangePollingIntervalMs(60000)
.flushIntervalMs(5000)
.maxPendingEvents(10000)
.disableDataCollection(false)
.build();
FeatureProvider provider = new GoFeatureFlagProvider(options);
OpenFeatureAPI.getInstance().setProviderAndWait(provider);
Client client = OpenFeatureAPI.getInstance().getClient("my-app");
// Create evaluation context with user attributes
EvaluationContext userContext = new MutableContext("user-12345")
.add("email", "john@example.com")
.add("firstname", "John")
.add("lastname", "Doe")
.add("admin", true)
.add("plan", "enterprise")
.add("age", 30)
.add("anonymous", false);
// Boolean flag evaluation
Boolean showNewFeature = client.getBooleanValue("new-checkout-flow", false, userContext);
if (showNewFeature) {
System.out.println("Showing new checkout flow");
}
// String flag with details
FlagEvaluationDetails screamDetails = client.getStringDetails(
"scream-level-feature", "low", userContext);
System.out.printf("Scream level: %s, reason: %s, variant: %s%n",
screamDetails.getValue(),
screamDetails.getReason(),
screamDetails.getVariant());
// Integer flag
Integer maxItems = client.getIntegerValue("max-cart-items", 10, userContext);
System.out.printf("Max cart items: %d%n", maxItems);
// Object flag
Value configValue = client.getObjectValue("dashboard-config", Value.objectToValue(Map.of()), userContext);
System.out.printf("Dashboard config: %s%n", configValue.asStructure());
// Cleanup
OpenFeatureAPI.getInstance().shutdown();
}
}
```
## Python SDK
Use the OpenFeature Python SDK with GO Feature Flag provider.
```python
from gofeatureflag_python_provider.provider import GoFeatureFlagProvider
from gofeatureflag_python_provider.options import GoFeatureFlagOptions
from openfeature import api
from openfeature.evaluation_context import EvaluationContext
from openfeature.flag_evaluation import FlagEvaluationDetails
# Initialize the provider
goff_provider = GoFeatureFlagProvider(
options=GoFeatureFlagOptions(
endpoint="http://localhost:1031/",
api_key="my-api-key",
timeout=10000,
)
)
# Set the provider globally
api.set_provider(goff_provider)
# Get a client
client = api.get_client(domain="my-python-app")
# Create evaluation context with user attributes
user_context = EvaluationContext(
targeting_key="user-12345",
attributes={
"email": "john@example.com",
"firstname": "John",
"lastname": "Doe",
"admin": True,
"plan": "enterprise",
"age": 30,
"anonymous": False,
"company_info": {"name": "Acme Corp", "size": 500},
"labels": ["beta", "premium"],
},
)
# Boolean flag evaluation
show_new_feature = client.get_boolean_value(
flag_key="new-checkout-flow",
default_value=False,
evaluation_context=user_context,
)
if show_new_feature:
print("Showing new checkout flow")
# String flag with details
scream_details: FlagEvaluationDetails = client.get_string_details(
flag_key="scream-level-feature",
default_value="low",
evaluation_context=user_context,
)
print(f"Scream level: {scream_details.value}, reason: {scream_details.reason}")
# Number flag
discount = client.get_float_value(
flag_key="discount-percentage",
default_value=0.0,
evaluation_context=user_context,
)
print(f"Discount: {discount}%")
# Object flag
dashboard_config = client.get_object_value(
flag_key="dashboard-config",
default_value={},
evaluation_context=user_context,
)
print(f"Dashboard config: {dashboard_config}")
```
## REST API - Flag Evaluation
The relay proxy exposes REST endpoints for direct flag evaluation without SDKs.
```bash
# Evaluate a single flag
curl -X POST http://localhost:1031/v1/feature/new-checkout-flow/eval \
-H "Content-Type: application/json" \
-H "X-API-Key: my-evaluation-api-key" \
-d '{
"evaluationContext": {
"key": "user-12345",
"custom": {
"email": "john@example.com",
"admin": true,
"plan": "enterprise"
}
},
"defaultValue": false
}'
# Response:
# {
# "trackEvents": true,
# "variationType": "enabled",
# "failed": false,
# "version": "1.0.0",
# "reason": "TARGETING_MATCH",
# "errorCode": "",
# "value": true,
# "cacheable": true
# }
# Evaluate all flags for a user
curl -X POST http://localhost:1031/v1/allflags \
-H "Content-Type: application/json" \
-H "X-API-Key: my-evaluation-api-key" \
-d '{
"evaluationContext": {
"key": "user-12345",
"custom": {
"email": "john@example.com",
"admin": true,
"plan": "enterprise"
}
}
}'
# Response:
# {
# "flags": {
# "new-checkout-flow": {
# "value": true,
# "variationType": "enabled",
# "reason": "TARGETING_MATCH"
# },
# "scream-level-feature": {
# "value": "high",
# "variationType": "high",
# "reason": "TARGETING_MATCH"
# }
# },
# "valid": true
# }
# OFREP single flag evaluation (OpenFeature Remote Evaluation Protocol)
curl -X POST http://localhost:1031/ofrep/v1/evaluate/flags/new-checkout-flow \
-H "Content-Type: application/json" \
-H "Authorization: Bearer my-evaluation-api-key" \
-d '{
"context": {
"targetingKey": "user-12345",
"email": "john@example.com",
"admin": true
}
}'
# OFREP bulk evaluation
curl -X POST http://localhost:1031/ofrep/v1/evaluate/flags \
-H "Content-Type: application/json" \
-H "Authorization: Bearer my-evaluation-api-key" \
-d '{
"context": {
"targetingKey": "user-12345",
"email": "john@example.com"
}
}'
```
## REST API - Management and Monitoring
Monitor and manage the relay proxy using admin and monitoring endpoints.
```bash
# Health check endpoint
curl http://localhost:1031/health
# Response: {"initialized":true}
# Get proxy info
curl http://localhost:1031/info
# Response: {"cacheRefresh":"2024-01-15T10:30:00Z"}
# Prometheus metrics endpoint
curl http://localhost:1031/metrics
# Returns Prometheus-formatted metrics
# Force refresh flag configuration (requires admin API key)
curl -X POST http://localhost:1031/admin/v1/retriever/refresh \
-H "X-API-Key: admin-api-key"
# Response: {"refreshed":true}
# Check for flag configuration changes (polling endpoint)
curl http://localhost:1031/v1/flag/change \
-H "X-API-Key: my-evaluation-api-key"
# Response: {"hash":1234567890,"flags":{"new-checkout-flow":1,"scream-level":2}}
# Get flag configurations for in-process evaluation
curl -X POST http://localhost:1031/v1/flag/configuration \
-H "Content-Type: application/json" \
-H "X-API-Key: my-evaluation-api-key" \
-d '{"flags":["new-checkout-flow","scream-level-feature"]}'
```
## Targeting Rules with nikunjy/rules Format
Create complex targeting rules using the nikunjy/rules query format.
```yaml
# flag-config.goff.yaml
premium-features:
variations:
full: true
limited: false
targeting:
# Target specific user by email
- name: specific-user
query: email eq "vip@example.com"
variation: full
# Target admin users
- name: admin-users
query: admin eq true
variation: full
# Target enterprise plan users
- name: enterprise-users
query: plan eq "enterprise"
variation: full
# Target users with specific domain
- name: company-domain
query: email ew "@bigcorp.com"
variation: full
# Complex multi-condition rule
- name: qualified-beta-users
query: (beta eq true) and (age ge 18) and (country in ["US", "CA", "UK"])
percentage:
full: 50
limited: 50
# Target non-anonymous users with minimum age
- name: verified-adults
query: (anonymous ne true) and (age gt 21)
variation: full
# String operations
- name: test-accounts
query: email sw "test" or email co "+test"
variation: full
disable: false # Rule is active
defaultRule:
variation: limited
# Available operators:
# eq, == : equals
# ne, != : not equals
# lt, < : less than
# gt, > : greater than
# le, <= : less than or equal
# ge, >= : greater than or equal
# co : contains
# sw : starts with
# ew : ends with
# in : in list
# pr : present (field exists)
# not : logical not
# and : logical and
# or : logical or
```
## Targeting Rules with JsonLogic Format
Create targeting rules using JsonLogic for more complex logic.
```yaml
# flag-config.goff.yaml
advanced-features:
variations:
enabled: true
disabled: false
targeting:
# Simple equality check
- name: specific-user
query: '{"==": [{"var": "email"}, "vip@example.com"]}'
variation: enabled
# Check if user is admin
- name: admin-users
query: '{"==": [{"var": "admin"}, true]}'
variation: enabled
# Check if value is in array
- name: premium-plans
query: '{"in": [{"var": "plan"}, ["enterprise", "business"]]}'
variation: enabled
# Complex AND condition
- name: qualified-users
query: '{"and": [{">=": [{"var": "age"}, 18]}, {"==": [{"var": "verified"}, true]}, {"in": [{"var": "country"}, ["US", "CA"]]}]}'
variation: enabled
# OR condition with nested checks
- name: special-access
query: '{"or": [{"==": [{"var": "admin"}, true]}, {"and": [{"==": [{"var": "beta"}, true]}, {">=": [{"var": "tenure_months"}, 12]}]}]}'
variation: enabled
# String operations
- name: internal-users
query: '{"endsWith": [{"var": "email"}, "@company.com"]}'
variation: enabled
# Numeric comparison
- name: high-value-users
query: '{">=": [{"var": "lifetime_value"}, 1000]}'
percentage:
enabled: 80
disabled: 20
defaultRule:
variation: disabled
```
## Progressive Rollout
Gradually release features to users over time with automatic percentage increases.
```yaml
# flag-config.goff.yaml
new-payment-system:
variations:
new: true
legacy: false
defaultRule:
progressiveRollout:
initial:
variation: legacy
percentage: 0
date: 2024-01-15T00:00:00Z
end:
variation: new
percentage: 100
date: 2024-01-22T00:00:00Z
# Timeline:
# Before Jan 15: 100% legacy (0% new)
# Jan 15: 0% new, starts progressive rollout
# Jan 16: ~14% new, 86% legacy
# Jan 17: ~28% new, 72% legacy
# Jan 18: ~42% new, 58% legacy
# Jan 19: ~57% new, 43% legacy
# Jan 20: ~71% new, 29% legacy
# Jan 21: ~85% new, 15% legacy
# Jan 22+: 100% new (0% legacy)
# Partial rollout (keep some users on legacy)
partial-migration:
variations:
new: true
legacy: false
defaultRule:
progressiveRollout:
initial:
variation: legacy
percentage: 20 # Start at 20% new, 80% legacy
date: 2024-02-01T00:00:00Z
end:
variation: new
percentage: 80 # End at 80% new, 20% legacy
date: 2024-02-15T00:00:00Z
# Progressive rollout on specific targeting rule
targeted-rollout:
variations:
new: true
legacy: false
targeting:
- name: enterprise-progressive
query: plan eq "enterprise"
progressiveRollout:
initial:
variation: legacy
date: 2024-01-10T00:00:00Z
end:
variation: new
date: 2024-01-20T00:00:00Z
defaultRule:
variation: legacy
```
## Scheduled Rollout
Schedule flag configuration changes for specific dates and times.
```yaml
# flag-config.goff.yaml
black-friday-sale:
variations:
sale: true
normal: false
defaultRule:
variation: normal
scheduledRollout:
# Enable sale on Black Friday
- date: 2024-11-29T00:00:00-05:00
defaultRule:
variation: sale
# Disable sale after Cyber Monday
- date: 2024-12-03T00:00:00-05:00
defaultRule:
variation: normal
# Multi-stage feature rollout
staged-release:
variations:
off: false
beta: true
ga: true
targeting: []
defaultRule:
variation: off
scheduledRollout:
# Stage 1: Enable for internal testing
- date: 2024-03-01T09:00:00Z
targeting:
- name: internal-testers
query: email ew "@company.com"
variation: beta
# Stage 2: Expand to beta users
- date: 2024-03-08T09:00:00Z
targeting:
- name: internal-testers
query: email ew "@company.com"
variation: beta
- name: beta-testers
query: beta eq true
variation: beta
# Stage 3: 50% rollout to all users
- date: 2024-03-15T09:00:00Z
defaultRule:
percentage:
off: 50
ga: 50
# Stage 4: Full GA release
- date: 2024-03-22T09:00:00Z
targeting: []
defaultRule:
variation: ga
# Update targeting rule by name
dynamic-targeting:
variations:
enabled: true
disabled: false
targeting:
- name: percentage-rule
query: targetingKey pr true
percentage:
enabled: 10
disabled: 90
defaultRule:
variation: disabled
scheduledRollout:
# Increase percentage over time
- date: 2024-04-01T00:00:00Z
targeting:
- name: percentage-rule
percentage:
enabled: 25
disabled: 75
- date: 2024-04-08T00:00:00Z
targeting:
- name: percentage-rule
percentage:
enabled: 50
disabled: 50
- date: 2024-04-15T00:00:00Z
targeting:
- name: percentage-rule
percentage:
enabled: 100
disabled: 0
```
## Experimentation (A/B Testing)
Run time-limited experiments with automatic start and end dates.
```yaml
# flag-config.goff.yaml
checkout-button-experiment:
variations:
control: "Buy Now"
variant_a: "Add to Cart"
variant_b: "Purchase"
defaultRule:
percentage:
control: 34
variant_a: 33
variant_b: 33
experimentation:
start: 2024-02-01T00:00:00Z
end: 2024-02-28T23:59:59Z
# Outside experiment dates: returns default value from SDK
# During experiment: splits traffic according to percentages
# Experimentation with targeting
premium-pricing-test:
variations:
current_pricing:
monthly: 9.99
yearly: 99.99
new_pricing:
monthly: 12.99
yearly: 119.99
targeting:
- name: new-users-only
query: created_at gt "2024-01-01"
percentage:
current_pricing: 50
new_pricing: 50
defaultRule:
variation: current_pricing
experimentation:
start: 2024-03-01T00:00:00Z
end: 2024-03-31T23:59:59Z
trackEvents: true # Ensure events are exported for analysis
metadata:
experiment_id: "pricing-test-q1-2024"
hypothesis: "Higher prices will not significantly impact conversion"
```
## Multi-Team Flag Sets
Configure separate flag sets for different teams with isolated configurations.
```yaml
# goff-proxy.yaml
server:
mode: http
port: 1031
logLevel: INFO
flagSets:
# Frontend team configuration
- name: frontend
apiKeys:
- "frontend-api-key-prod"
- "frontend-api-key-staging"
retrievers:
- kind: github
repositorySlug: my-org/frontend-flags
path: flags/production.yaml
branch: main
token: ${GITHUB_TOKEN}
pollingInterval: 30000
exporters:
- kind: s3
bucket: frontend-feature-flag-events
notifiers:
- kind: slack
webhookUrl: "https://hooks.slack.com/services/frontend-channel"
# Backend team configuration
- name: backend
apiKeys:
- "backend-api-key-prod"
retrievers:
- kind: s3
bucket: backend-feature-flags
item: flags/production.yaml
pollingInterval: 60000
exporters:
- kind: kafka
kafka:
addresses:
- "kafka-1.example.com:9092"
- "kafka-2.example.com:9092"
topic: "backend-flag-events"
notifiers:
- kind: webhook
endpointUrl: "https://backend-service.example.com/webhooks/flag-changes"
# Mobile team configuration
- name: mobile
apiKeys:
- "mobile-ios-api-key"
- "mobile-android-api-key"
retrievers:
- kind: http
url: "https://config-server.example.com/mobile/flags.yaml"
headers:
Authorization: "Bearer ${CONFIG_SERVER_TOKEN}"
pollingInterval: 120000
evaluationContextEnrichment:
platform: mobile
exporters:
- kind: googlecloudstorage
bucket: mobile-analytics
format: parquet
```
## CLI Linting and Evaluation
Use the GO Feature Flag CLI to lint configurations and evaluate flags locally.
```bash
# Install the CLI
go install github.com/thomaspoignant/go-feature-flag/cmd/cli@latest
# Lint flag configuration files
go-feature-flag-cli lint --input-file=flag-config.yaml
# Output: ✅ flag-config.yaml is valid
# Lint with JSON output
go-feature-flag-cli lint --input-file=flag-config.yaml --format=json
# Output: {"valid":true,"errors":[]}
# Lint multiple files
go-feature-flag-cli lint --input-file=flags/*.yaml
# Evaluate a flag locally
go-feature-flag-cli evaluate \
--config=flag-config.yaml \
--flag=new-checkout-flow \
--ctx='{"targetingKey":"user-123","admin":true}'
# Output: {"value":true,"variationType":"enabled","reason":"TARGETING_MATCH"}
# Evaluate all flags for a context
go-feature-flag-cli evaluate \
--config=flag-config.yaml \
--ctx='{"targetingKey":"user-123","plan":"enterprise"}'
# Output: All flag evaluations as JSON
# Generate flag manifest for type-safe SDKs
go-feature-flag-cli generate manifest \
--config=flag-config.yaml \
--output=flag-manifest.json
# Use in CI/CD pipeline
go-feature-flag-cli lint --input-file=flag-config.yaml || exit 1
```
## WebSocket Real-time Updates
Subscribe to real-time flag change notifications via WebSocket.
```javascript
// JavaScript WebSocket client for flag change notifications
const ws = new WebSocket("ws://localhost:1031/ws/v1/flag/change?apiKey=my-api-key");
ws.onopen = () => {
console.log("Connected to GO Feature Flag WebSocket");
};
ws.onmessage = (event) => {
const changes = JSON.parse(event.data);
console.log("Flag changes received:", changes);
// changes structure:
// {
// "deleted": {"old-flag": {...}},
// "added": {"new-flag": {...}},
// "updated": {
// "updated-flag": {
// "before": {...},
// "after": {...}
// }
// }
// }
// Trigger local cache refresh or UI update
if (changes.updated["my-critical-flag"]) {
refreshFeatureFlags();
}
};
ws.onerror = (error) => {
console.error("WebSocket error:", error);
};
ws.onclose = (event) => {
console.log("WebSocket closed:", event.code, event.reason);
// Implement reconnection logic
setTimeout(() => reconnectWebSocket(), 5000);
};
```
## Custom Bucketing
Use custom keys for consistent flag evaluation across related entities.
```yaml
# flag-config.goff.yaml
# Bucket by team ID so all team members see same variation
team-feature:
bucketingKey: teamId
variations:
enabled: true
disabled: false
defaultRule:
percentage:
enabled: 50
disabled: 50
# Bucket by organization for B2B features
enterprise-feature:
bucketingKey: organizationId
variations:
basic:
maxUsers: 10
features: ["core"]
advanced:
maxUsers: 100
features: ["core", "analytics", "export"]
targeting:
- query: plan eq "enterprise"
variation: advanced
defaultRule:
variation: basic
# Bucket by session for consistent anonymous experience
anonymous-experiment:
bucketingKey: sessionId
variations:
control: { layout: "grid" }
variant: { layout: "list" }
defaultRule:
percentage:
control: 50
variant: 50
```
GO Feature Flag provides a comprehensive solution for feature flag management that scales from simple boolean toggles to complex multi-variant experiments with sophisticated targeting. The combination of file-based configuration, the OpenFeature standard, and multi-language SDK support makes it suitable for organizations of all sizes seeking a self-hosted, vendor-neutral feature flagging solution.
The relay proxy architecture enables centralized flag management while supporting both remote and in-process evaluation modes to meet different latency and reliability requirements. With built-in support for progressive rollouts, scheduled changes, experimentation, and real-time notifications, teams can safely deploy features, run A/B tests, and quickly respond to issues by toggling flags without code deployments.