Skip to main content

Standalone @flaggr/sdk package with pluggable OTEL instrumentation

Last updated October 3, 2026

TypeScript SDK (@flaggr/sdk)

The standalone Flaggr SDK for TypeScript. Includes a core client, React hooks, and pluggable OpenTelemetry instrumentation.

Installation

npm install @flaggr/sdk

Or load it straight from the CDN — no bundler needed:

<script src="https://cdn.jsdelivr.net/npm/@flaggr/sdk@0/dist/browser.global.js"
        data-service-id="web-app"
        data-api-key="fgr_your_token"
        data-environment="production"
        defer></script>

The data-* attributes auto-configure the global client — that script tag is the entire setup (~6 KB gzipped). Anywhere later on the page:

<script type="module">
  const enabled = await flaggr.flag('checkout-v2', false)
</script>

ES modules via CDN work too:

<script type="module">
  import { createFlaggr } from 'https://cdn.jsdelivr.net/npm/@flaggr/sdk@0.4.0/+esm'
  const client = createFlaggr({ serviceId: 'web-app', apiKey: 'fgr_your_token' })
</script>

Quick Start

import { createFlaggr } from '@flaggr/sdk'
 
const client = createFlaggr({
  serviceId: 'web-app',
  apiKey: 'fgr_your_token',
})
 
const isEnabled = await client.getBooleanValue('checkout-v2', false)

Remote Configuration (zero-config setup)

Set remoteConfig: true and the SDK fetches service-level settings from GET /api/sdk-config — environment, update mode, cache TTL, and telemetry opt-in all come from the service's settings.sdk instead of being repeated in client code:

const client = createFlaggr({
  serviceId: 'web-app',
  apiKey: 'fgr_your_token',
  remoteConfig: true, // pulls environment/updateMode/telemetry from Flaggr
})

Explicit fields always win — remote values only fill gaps. The settings response is cached in localStorage (60s fresh window, stale-while-revalidate), and the flag bootstrap is persisted as a separate snapshot — repeat page loads evaluate instantly from disk with zero requests, revalidating in the background. When the service config sets sdk.telemetry: true, the telemetry plugin attaches itself.

Snapshot durability guarantees:

  • Bustable: client.clearPersistedConfig() drops settings + all flag snapshots for the service; schema-versioned records self-invalidate on SDK upgrades; snapshots expire after 7 days.
  • Isolated: snapshots are keyed serviceId + environment + auth-scope (anonymous vs API-key hash) — an authed session's private flags can never leak into an anonymous session.
  • Managed: writes >4MB are skipped; quota errors evict oldest flaggr:* snapshots and retry once; corrupt/stale records are detected and removed.
  • Fresh: the server returns a bootstrapVersion content hash — identical payloads skip re-apply and re-persist entirely; SSE configuration_sync, configuration_delta, and flag-update messages keep the snapshot current.

The endpoint also returns a bootstrap flag payload — the service's flag configurations for the environment — so the SDK evaluates locally with zero additional round trips after the config fetch. Authenticated requests (Authorization: Bearer) get the full flag set; unauthenticated requests get isPublic flags only (same trust boundary as POST /api/flags/evaluate).

Script tag:

<script src="https://cdn.jsdelivr.net/npm/@flaggr/sdk@0/dist/browser.global.js"
        data-service-id="web-app" data-api-key="fgr_..." data-remote-config defer></script>

Browser Telemetry (opt-in)

Attach the telemetry plugin to stream evaluation stats, Core Web Vitals, and error correlation to the platform — same ingest surface as the hosted web provider, so CDN installs get identical analytics:

import { createFlaggr, flaggrTelemetry } from '@flaggr/sdk'
 
const client = createFlaggr({
  serviceId: 'web-app',
  apiKey: 'fgr_your_token',
  plugins: [flaggrTelemetry()],
})

Via the script tag, add data-telemetry:

<script src="https://cdn.jsdelivr.net/npm/@flaggr/sdk@0/dist/browser.global.js"
        data-service-id="web-app" data-api-key="fgr_..." data-telemetry defer></script>

Options: { vitals, errors, beacon, flushIntervalMs } — vitals/errors/beacon default on, flush every 30s plus sendBeacon on page hide.

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.evaluate<boolean>('checkout-v2', false)
 
// detail.value       → true
// detail.reason      → "TARGETING_MATCH"
// detail.variant     → "enabled"
// detail.errorMessage → undefined (present on errors)

With React

import { FlaggrProvider, useBooleanFlag } from '@flaggr/sdk/react'
 
function App() {
  return (
    <FlaggrProvider config={{
      serviceId: 'web-app',
      apiKey: 'fgr_your_token',
    }}>
      <Checkout />
    </FlaggrProvider>
  )
}
 
function Checkout() {
  const useNewFlow = useBooleanFlag('checkout-v2', false)
  return useNewFlow ? <CheckoutV2 /> : <CheckoutClassic />
}

Available Hooks

HookReturnsDescription
useBooleanFlag(key, default)booleanBoolean flag value
useStringFlag(key, default)stringString flag value
useNumberFlag(key, default)numberNumber flag value
useObjectFlag<T>(key, default)TTyped object flag value
useConnectionState()ConnectionStateCurrent 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 occurs

Evaluation Reasons and Errors

ReasonMeaningRecovery
STATICFlag returned its configured default or static valueNo action needed
DEFAULTSDK returned a configured offline/default valueCheck server reachability if unexpected
TARGETING_MATCHTargeting rules matched the supplied contextNo action needed
SPLITRollout or variant allocation selected this valueNo action needed
DISABLEDFlag is disabledEnable the flag or update the fallback
NOT_FOUNDFlag key does not existCheck the key spelling and service ID
ERRORNetwork/server error or invalid responseCheck connectivity and errorMessage

Custom Error Handling

Use the plugin system for logging and alerting on errors:

const client = createFlaggr({
  serviceId: 'web-app',
  apiKey: 'fgr_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 },
      })
    },
  }],
})

Streaming Reconnection

Evaluation methods fail safely by returning the provided default value and reporting errors through plugins. The SDK does not expose per-evaluation retry configuration. When streaming is enabled, browser EventSource reconnects automatically after connection loss:

const client = createFlaggr({
  serviceId: 'web-app',
  apiKey: 'fgr_your_token',
  enableStreaming: true,
})

When streaming is enabled, the SDK updates its connection state to error on stream errors and relies on the browser's EventSource reconnection behavior.

With OpenTelemetry

The SDK supports pluggable OTEL instrumentation. Install the peer dependency:

npm install @opentelemetry/api

Basic OTEL Setup

import { createFlaggr } from '@flaggr/sdk'
import { otelPlugin } from '@flaggr/sdk/otel'
 
const client = createFlaggr({
  serviceId: 'web-app',
  apiKey: 'fgr_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

MetricTypeDescription
flaggr.evaluations.totalCounterTotal flag evaluations
flaggr.evaluations.durationHistogramEvaluation latency (ms)
flaggr.evaluations.errorsCounterEvaluation errors
flaggr.cache.hitsCounterCache hits
flaggr.cache.missesCounterCache misses
flaggr.flag_changes.totalCounterFlag change events
flaggr.connections.activeUpDownCounterActive connections

Exported Spans

Each flag evaluation creates a span named flaggr.evaluate with attributes:

AttributeDescription
feature_flag.keyFlag key being evaluated
feature_flag.provider_nameAlways "flaggr"
feature_flag.valueEvaluated value
feature_flag.reasonEvaluation reason
feature_flag.variantVariant key (if applicable)

Browser Analytics & Web Vitals Telemetry

The Flaggr client SDK (@flaggr/sdk / src/lib/client) provides built-in, zero-overhead browser analytics that link Core Web Vitals and client runtime errors directly to active feature flags.

Key Capabilities

  1. Zero-Allocation Evaluation Summaries: Tracks evaluation counts, cache hits, cache misses, and average latency locally without per-call network overhead.
  2. Automated Core Web Vitals Tracking: Uses browser PerformanceObserver to track Largest Contentful Paint (LCP), Cumulative Layout Shift (CLS), and First Contentful Paint (FCP), automatically tagging each metric with active flag keys and variants.
  3. Client-Side Error Correlation: Records unhandled errors or caught UI exceptions alongside the user's active flag evaluations for fast regression attribution.
  4. Reliable Background Ingestion: Batches telemetry and flushes via navigator.sendBeacon (with fetch keepalive fallback), ensuring telemetry reaches the server even during page unloads without dropping frames.
  5. Client Diagnostics & Profiling: Provides getDiagnostics() to monitor evaluation throughput (over 7M+ ops/sec for L1 cache hits) and cache hit ratios.

Initializing Analytics

import { createFlaggr, AnalyticsClient } from '@flaggr/sdk'
 
const client = createFlaggr({
  serviceId: 'web-app',
  apiKey: 'fgr_your_token',
  analytics: {
    enabled: true,
    captureWebVitals: true,  // Auto-tracks LCP, CLS, FCP via PerformanceObserver
    captureErrors: true,      // Auto-captures unhandled window errors
    useBeacon: true,          // Use navigator.sendBeacon during page unload
    flushIntervalMs: 30000,   // Flush batch every 30 seconds
    maxBatchSize: 100,        // Flush when 100 events accumulate
    sampleRate: 1.0,          // 100% of sessions (0.0 to 1.0)
  },
})

Manual Metric & Error Tracking

You can also explicitly track custom metrics, Core Web Vitals, or handled errors:

import { getFlaggrAnalytics } from '@flaggr/sdk'
 
const analytics = getFlaggrAnalytics()
 
// Track Web Vitals manually (e.g. via web-vitals package)
analytics.trackWebVital({ name: 'INP', value: 48, rating: 'good' })
 
// Correlate caught exceptions with active flags
try {
  executePaymentFlow()
} catch (err: any) {
  analytics.trackError(err.message, err.stack, { checkoutStep: 'payment' })
  throw err
}

SDK Diagnostics & Profiling

Inspect real-time evaluation throughput and cache performance at any time:

import { getFlaggrDiagnostics } from '@flaggr/sdk'
 
const diag = getFlaggrDiagnostics()
console.log(`Evaluations: ${diag.totalEvaluations}`)
console.log(`Cache Hit Ratio: ${(diag.cacheHitRatio * 100).toFixed(1)}%`)
console.log(`Active Flags in Memory: ${diag.trackedFlagsCount}`)

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://api.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)
  updateMode?: 'stream' | 'batch' | 'poll'  // Update delivery (default: poll)
  batchIntervalMs?: number  // Batch refresh cadence (default: 2000)
  enableStreaming?: boolean  // Deprecated — use updateMode: 'stream'
  defaults?: Record<string, FlagValue>  // Offline defaults
}

Update Modes

The SDK's update delivery can be switched at runtime — stream applies SSE pushes instantly with zero requests, batch revalidates all watched flags in one grouped request per interval, and poll relies on per-flag fetches when the cache expires:

const client = createFlaggr({
  serviceId: 'web-app',
  apiKey: 'fgr_your_token',
  updateMode: 'batch',
  batchIntervalMs: 1500,
})
 
client.setUpdateMode('stream') // tears down batch, opens SSE
 
// Per-request telemetry for instrumentation
const client = createFlaggr({
  serviceId: 'web-app',
  apiKey: 'fgr_your_token',
  plugins: [{
    name: 'telemetry',
    onRequest: (info) => {
      // info: { url, durationMs, ok, requestBytes, responseBytes }
      metrics.record(info.durationMs)
    },
  }],
})

Offline Defaults

Provide fallback values for when the SDK cannot reach the server:

const client = createFlaggr({
  serviceId: 'web-app',
  apiKey: 'fgr_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.