Getting Started

Table of contents

  1. Installation
  2. Basic Usage
    1. Setting Up with a Provider
    2. Setting Up for Testing
  3. Evaluating Flags
    1. Boolean Flags
    2. String Flags
    3. Numeric Flags
    4. Object Flags
    5. Detailed Evaluation
  4. Using Evaluation Context
    1. Setting Global Context
    2. Scoped Context
  5. Factory Methods
    1. From Any OpenFeature Provider
    2. With Domain Isolation
    3. With Initial Hooks
    4. With Multiple Providers
  6. Tracking Events
  7. Event Handlers
  8. Next Steps

Installation

Maven Central

Add the following to your build.sbt, replacing <version> with the version shown in the badge above:

// Core library (includes OpenFeature SDK)
libraryDependencies += "io.github.etacassiopeia" %% "zio-openfeature-core" % "<version>"

// For testing
libraryDependencies += "io.github.etacassiopeia" %% "zio-openfeature-testkit" % "<version>" % Test

You’ll also need an OpenFeature provider for your feature flag service:

// Example: flagd provider
libraryDependencies += "dev.openfeature.contrib.providers" % "flagd" % "0.8.9"

// Or: LaunchDarkly
libraryDependencies += "dev.openfeature.contrib.providers" % "launchdarkly" % "1.1.0"

See the OpenFeature ecosystem for all available providers.

Basic Usage

Setting Up with a Provider

import zio.*
import zio.openfeature.*
import dev.openfeature.contrib.providers.flagd.FlagdProvider

object MyApp extends ZIOAppDefault:

  val program = for
    enabled <- FeatureFlags.boolean("my-feature", default = false)
    _       <- ZIO.when(enabled)(Console.printLine("Feature enabled!"))
  yield ()

  def run = program.provide(
    Scope.default >>> FeatureFlags.fromProvider(new FlagdProvider())
  )

Setting Up for Testing

import zio.*
import zio.openfeature.*
import zio.openfeature.testkit.*

object TestApp extends ZIOAppDefault:

  val program = for
    enabled <- FeatureFlags.boolean("my-feature", default = false)
    _       <- ZIO.when(enabled)(Console.printLine("Feature enabled!"))
  yield ()

  def run = program.provide(
    Scope.default >>> TestFeatureProvider.layer(Map("my-feature" -> true))
  )

Evaluating Flags

Boolean Flags

val enabled: ZIO[FeatureFlags, FeatureFlagError, Boolean] =
  FeatureFlags.boolean("feature-toggle", default = false)

String Flags

val variant: ZIO[FeatureFlags, FeatureFlagError, String] =
  FeatureFlags.string("button-color", default = "blue")

Numeric Flags

val limit: ZIO[FeatureFlags, FeatureFlagError, Int] =
  FeatureFlags.int("max-items", default = 100)

val rate: ZIO[FeatureFlags, FeatureFlagError, Double] =
  FeatureFlags.double("sample-rate", default = 0.1)

val count: ZIO[FeatureFlags, FeatureFlagError, Long] =
  FeatureFlags.long("max-bytes", default = 1000000L)

Object Flags

val config: ZIO[FeatureFlags, FeatureFlagError, Map[String, Any]] =
  FeatureFlags.obj("feature-config", default = Map("timeout" -> 30))

Detailed Evaluation

Get full resolution details including variant, reason, and metadata:

val details: ZIO[FeatureFlags, FeatureFlagError, FlagResolution[Boolean]] =
  FeatureFlags.booleanDetails("feature", default = false)

details.map { resolution =>
  println(s"Value: ${resolution.value}")
  println(s"Variant: ${resolution.variant}")
  println(s"Reason: ${resolution.reason}")
  println(s"Flag Key: ${resolution.flagKey}")
}

Using Evaluation Context

Pass user and environment information for targeted flag evaluation:

// Create context with targeting key (user ID)
val ctx = EvaluationContext("user-123")
  .withAttribute("plan", "premium")
  .withAttribute("country", "US")
  .withAttribute("beta", true)

// Evaluate with context
FeatureFlags.boolean("premium-feature", default = false, ctx)

Setting Global Context

Apply context to all evaluations:

val globalCtx = EvaluationContext.empty
  .withAttribute("app_version", "2.0.0")
  .withAttribute("environment", "production")

FeatureFlags.setGlobalContext(globalCtx)

Scoped Context

Apply context to a block of code:

val requestCtx = EvaluationContext("user-456")
  .withAttribute("session_id", sessionId)

FeatureFlags.withContext(requestCtx) {
  // All evaluations in this block use requestCtx
  for
    a <- FeatureFlags.boolean("feature-a", default = false)
    b <- FeatureFlags.string("feature-b", default = "control")
  yield (a, b)
}

Factory Methods

From Any OpenFeature Provider

import dev.openfeature.sdk.FeatureProvider

val provider: FeatureProvider = // any OpenFeature provider

val layer = FeatureFlags.fromProvider(provider)

With Domain Isolation

For multi-provider setups, use domains to isolate providers:

val layer = FeatureFlags.fromProviderWithDomain(provider, "my-domain")

With Initial Hooks

val hooks = List(
  FeatureHook.logging(),
  FeatureHook.metrics((k, d, s) => ZIO.unit)
)

val layer = FeatureFlags.fromProviderWithHooks(provider, hooks)

With Multiple Providers

Combine multiple providers using the SDK’s MultiProvider support:

val layer = FeatureFlags.fromMultiProvider(List(localProvider, remoteProvider))

Tracking Events

Track user actions for analytics and experimentation:

// Simple event tracking
FeatureFlags.track("button-clicked")

// Track with user context
FeatureFlags.track("purchase", EvaluationContext("user-123"))

// Track with event details
val details = TrackingEventDetails(
  value = Some(99.99),
  attributes = Map("currency" -> "USD", "items" -> 3)
)
FeatureFlags.track("checkout", details)

Event Handlers

React to provider lifecycle events:

// Handle provider ready
FeatureFlags.onProviderReady { metadata =>
  ZIO.logInfo(s"Provider ${metadata.name} is ready")
}

// Handle configuration changes
FeatureFlags.onConfigurationChanged { (flags, metadata) =>
  ZIO.logInfo(s"Flags changed: ${flags.mkString(", ")}")
}

// Handle errors
FeatureFlags.onProviderError { (error, metadata) =>
  ZIO.logError(s"Provider error: ${error.getMessage}")
}

Next Steps


Copyright © 2026 Mohsen Zainalpour. Distributed under the Apache 2.0 license.

This site uses Just the Docs, a documentation theme for Jekyll.