Standalone @flaggr/sdk package with pluggable OTEL instrumentation
TypeScript SDK (@flaggr/sdk)
The standalone Flaggr SDK for TypeScript. Includes a core client, React hooks, and pluggable OpenTelemetry instrumentation.
Installation
npm install @flaggr/sdkQuick Start
import { createFlaggr } from '@flaggr/sdk'
const client = createFlaggr({
serviceId: 'web-app',
apiKey: 'flg_your_token',
})
const isEnabled = await client.getBooleanValue('checkout-v2', false)Core Client API
Flag Evaluation Methods
// Boolean flag
const enabled: boolean = await client.getBooleanValue('my-flag', false)
// String flag
const variant: string = await client.getStringValue('button-color', 'blue')
// Number flag
const limit: number = await client.getNumberValue('rate-limit', 100)
// Object flag (typed)
interface BannerConfig {
text: string
color: string
dismissible: boolean
}
const banner: BannerConfig = await client.getObjectValue<BannerConfig>(
'banner-config',
{ text: '', color: 'blue', dismissible: true }
)Evaluation with Context
Pass targeting context for per-user or per-segment evaluation:
const result = await client.getBooleanValue('checkout-v2', false, {
targetingKey: 'user-456',
email: 'bob@example.com',
plan: 'enterprise',
country: 'AU',
})Detailed Evaluation
When you need the full evaluation result (reason, variant, metadata):
const detail = await client.getBooleanDetails('checkout-v2', false)
// detail.value → true
// detail.reason → "TARGETING_MATCH"
// detail.variant → "enabled"
// detail.flagKey → "checkout-v2"
// detail.errorCode → undefined (present on errors)With React
import { FlaggrProvider, useBooleanFlag } from '@flaggr/sdk/react'
function App() {
return (
<FlaggrProvider config={{
serviceId: 'web-app',
apiKey: 'flg_your_token',
}}>
<Checkout />
</FlaggrProvider>
)
}
function Checkout() {
const useNewFlow = useBooleanFlag('checkout-v2', false)
return useNewFlow ? <CheckoutV2 /> : <CheckoutClassic />
}Available Hooks
| Hook | Returns | Description |
|---|---|---|
useBooleanFlag(key, default) | boolean | Boolean flag value |
useStringFlag(key, default) | string | String flag value |
useNumberFlag(key, default) | number | Number flag value |
useObjectFlag<T>(key, default) | T | Typed object flag value |
useConnectionState() | ConnectionState | Current connection state |
useRefreshFlags() | () => Promise<void> | Manual refresh function |
Hook Behavior
All flag hooks subscribe to real-time updates when streaming is enabled. When a flag value changes server-side, the hook re-renders the component automatically. If the SDK is disconnected, hooks return the default value you provided.
// ConnectionState is one of:
type ConnectionState = 'connecting' | 'connected' | 'disconnected' | 'error'
// Use in a status indicator
function ConnectionBadge() {
const state = useConnectionState()
return (
<span className={state === 'connected' ? 'text-green-500' : 'text-red-500'}>
{state}
</span>
)
}Error Handling
The SDK is designed to fail safely. Evaluation methods never throw -- they return the default value and report errors via the plugin system.
// This never throws, even if the server is unreachable
const value = await client.getBooleanValue('my-flag', false)
// → false (default) when an error occursError Codes
| Code | Meaning | Recovery |
|---|---|---|
FLAG_NOT_FOUND | Flag key does not exist | Check the key spelling and service ID |
PARSE_ERROR | Flag value type mismatch | Verify you are calling the right method (boolean/string/number) |
PROVIDER_NOT_READY | SDK not yet initialized | Wait for connected state or use await initializeFlaggr() |
GENERAL | Network/server error | SDK retries automatically; check connectivity |
TARGETING_KEY_MISSING | Context lacks targetingKey | Add targetingKey to evaluation context |
Custom Error Handling
Use the plugin system for logging and alerting on errors:
const client = createFlaggr({
serviceId: 'web-app',
apiKey: 'flg_your_token',
plugins: [{
name: 'error-reporter',
onEvaluateError(flagKey, error, durationMs) {
console.error(`Flag evaluation failed: ${flagKey}`, error)
// Send to your error tracking service
Sentry.captureException(error, {
tags: { flagKey, durationMs },
})
},
}],
})Retry and Reconnection
The SDK handles transient failures automatically with exponential backoff:
const client = createFlaggr({
serviceId: 'web-app',
apiKey: 'flg_your_token',
// Retry configuration (these are the defaults)
retryConfig: {
maxRetries: 3, // Max retry attempts per evaluation
initialDelayMs: 100, // First retry delay
maxDelayMs: 5000, // Maximum backoff delay
backoffMultiplier: 2, // Exponential backoff factor
},
// Streaming reconnection
enableStreaming: true,
streamReconnectDelayMs: 1000, // Initial reconnect delay for SSE/gRPC streams
})When streaming is enabled, the SDK reconnects automatically on connection loss. The reconnection delay doubles on each attempt (up to 30 seconds) and resets on successful connection.
With OpenTelemetry
The SDK supports pluggable OTEL instrumentation. Install the peer dependency:
npm install @opentelemetry/apiBasic OTEL Setup
import { createFlaggr } from '@flaggr/sdk'
import { otelPlugin } from '@flaggr/sdk/otel'
const client = createFlaggr({
serviceId: 'web-app',
apiKey: 'flg_your_token',
plugins: [otelPlugin()], // Uses global OTEL providers
})Custom Configuration
import { otelPlugin } from '@flaggr/sdk/otel'
plugins: [otelPlugin({
serviceName: 'checkout-service',
enableTracing: true,
enableMetrics: true,
metricPrefix: 'myapp.flags',
additionalAttributes: {
'deployment.environment': 'production',
'service.version': '1.2.3',
},
})]Bring Your Own Providers
import { MeterProvider } from '@opentelemetry/sdk-metrics'
import { otelPlugin } from '@flaggr/sdk/otel'
const meterProvider = new MeterProvider({
readers: [myMetricReader],
})
plugins: [otelPlugin({
meterProvider,
tracerProvider: myTracerProvider,
})]Exported Metrics
| Metric | Type | Description |
|---|---|---|
flaggr.evaluations.total | Counter | Total flag evaluations |
flaggr.evaluations.duration | Histogram | Evaluation latency (ms) |
flaggr.evaluations.errors | Counter | Evaluation errors |
flaggr.cache.hits | Counter | Cache hits |
flaggr.cache.misses | Counter | Cache misses |
flaggr.flag_changes.total | Counter | Flag change events |
flaggr.connections.active | UpDownCounter | Active connections |
Exported Spans
Each flag evaluation creates a span named flaggr.evaluate with attributes:
| Attribute | Description |
|---|---|
feature_flag.key | Flag key being evaluated |
feature_flag.provider_name | Always "flaggr" |
feature_flag.value | Evaluated value |
feature_flag.reason | Evaluation reason |
feature_flag.variant | Variant key (if applicable) |
Plugin System
The SDK supports a plugin architecture for extending behavior:
import type { FlaggrPlugin } from '@flaggr/sdk'
const myPlugin: FlaggrPlugin = {
name: 'my-plugin',
onInit(client) { /* SDK initialized */ },
onEvaluate(flagKey, context) { /* Before evaluation */ },
onEvaluateComplete(flagKey, result, durationMs) { /* After evaluation */ },
onEvaluateError(flagKey, error, durationMs) { /* On error */ },
onFlagChange(event) { /* Flag value changed */ },
onConnectionStateChange(state) { /* Connection state changed */ },
onDestroy() { /* SDK destroyed */ },
}
const client = createFlaggr({
serviceId: 'web-app',
plugins: [myPlugin],
})Plugin Lifecycle
createFlaggr()
├─ onInit() Called once after client creation
│
├─ evaluate('flag')
│ ├─ onEvaluate() Before evaluation
│ ├─ onEvaluateComplete() After successful evaluation
│ └─ onEvaluateError() On evaluation failure
│
├─ onFlagChange() When a flag value changes (streaming only)
├─ onConnectionStateChange() On connection state transitions
│
└─ client.destroy()
└─ onDestroy() Cleanup (close connections, flush metrics)
Configuration
interface FlaggrConfig {
apiUrl?: string // Default: 'https://flaggr.dev'
serviceId: string // Your service identifier
apiKey?: string // API token
environment?: string // Target environment
context?: EvaluationContext // Default evaluation context
plugins?: FlaggrPlugin[] // SDK plugins
cacheTtl?: number // Cache TTL in ms (default: 10000)
enableStreaming?: boolean // SSE real-time updates
defaults?: Record<string, FlagValue> // Offline defaults
retryConfig?: RetryConfig // Retry configuration
streamReconnectDelayMs?: number // Initial stream reconnect delay
}Offline Defaults
Provide fallback values for when the SDK cannot reach the server:
const client = createFlaggr({
serviceId: 'web-app',
apiKey: 'flg_your_token',
defaults: {
'checkout-v2': false,
'dark-mode': true,
'rate-limit': 100,
'banner-config': { text: 'Welcome', color: 'blue', dismissible: true },
},
})When the server is unreachable and the local cache is empty, the SDK returns values from defaults instead of the method-level default. This lets you centralize your fallback configuration.