diff --git a/README.md b/README.md index 95fd2615de3a..26345a543ea2 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ # [Flagsmith](https://flagsmith.com/) is an Open-Source Feature Flagging Tool to Ship Faster & Control Releases -Change the way your team releases software. Roll out, segment, and optimise—with granular control. Stay secure with on-premise and private cloud hosting. +Change the way your team releases software. Roll out, segment, and optimise—with granular control. Stay secure with on-premise and private cloud hosting. * Feature flags: Release features behind the safety of a feature flag * Make changes remotely: Easily toggle individual features on and off, and make changes without deploying new code diff --git a/docs/docs/flagsmith-integration/CLI.md b/docs/docs/flagsmith-integration/CLI.md new file mode 100644 index 000000000000..058885876f64 --- /dev/null +++ b/docs/docs/flagsmith-integration/CLI.md @@ -0,0 +1,48 @@ +--- +description: Flagsmith Command Line Interface (CLI) +sidebar_label: CLI +sidebar_position: 40 +--- + +# Flagsmith CLI + +Flagsmith has a [CLI tool](https://github.com/Flagsmith/flagsmith-cli) that you can use to help in your development +workflows. + +## Installation + +Install globally: + +```bash +npm install -g flagsmith-cli +``` + +## Sample Usage + +```bash +USAGE + $ flagsmith get [ENVIRONMENT] [-o ] [-a ] [-i ] + +ARGUMENTS + ENVIRONMENT The flagsmith environment key to use, + defaults to the environment variable FLAGSMITH_ENVIRONMENT + +FLAGS + -a, --api= The API URL to fetch the feature flags from + -i, --identity= The identity for which to fetch feature flags + -o, --output= [default: ./flagsmith.json] The file path output + +DESCRIPTION + Retrieve flagsmith feature flags from the Flagsmith API and output them to a file. + +EXAMPLES + $ FLAGSMITH_ENVIRONMENT=x flagsmith get + + $ flagsmith get + + $ flagsmith get --o ./my-file.json + + $ flagsmith get --a https://flagsmith.example.com/api/v1/ + + $ flagsmith get --i flagsmith_identity +``` diff --git a/docs/docs/flagsmith-integration/_category_.json b/docs/docs/flagsmith-integration/_category_.json new file mode 100644 index 000000000000..81002d33b78c --- /dev/null +++ b/docs/docs/flagsmith-integration/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Flagsmith Integration", + "position": 70, + "collapsed": true +} diff --git a/docs/docs/flagsmith-integration/client-side-sdks/_category_.json b/docs/docs/flagsmith-integration/client-side-sdks/_category_.json new file mode 100644 index 000000000000..fac485a57824 --- /dev/null +++ b/docs/docs/flagsmith-integration/client-side-sdks/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Client-Side SDKs", + "position": 20, + "collapsed": true +} diff --git a/docs/docs/flagsmith-integration/client-side-sdks/android.md b/docs/docs/flagsmith-integration/client-side-sdks/android.md new file mode 100644 index 000000000000..c1bd7b8f5358 --- /dev/null +++ b/docs/docs/flagsmith-integration/client-side-sdks/android.md @@ -0,0 +1,234 @@ +--- +title: Flagsmith Android/Kotlin SDK +sidebar_label: Android / Kotlin +description: Manage your Feature Flags and Remote Config in your Android applications. +slug: /clients/android +--- + +import CodeBlock from '@theme/CodeBlock'; import { AndroidVersion } from '@site/src/components/SdkVersions.js'; + +This SDK can be used for Android applications written in Kotlin. The source code for the client is available on +[GitHub](https://github.com/Flagsmith/flagsmith-kotlin-android-client/). + +## Installation + +### Gradle + +```groovy +repositories { + google() + mavenCentral() +} +``` + +In your project path `app/build.gradle` add a new dependency: + +{`implementation("com.flagsmith:flagsmith-kotlin-android-client:`}"{`)`} + +## Basic Usage + +The SDK is initialised against a single environment within a project on [https://flagsmith.com](https://flagsmith.com), +for example the Development or Production environment. You can find your Client-side Environment Key in the Environment +settings page. + +## Initialization + +### Within your Activity inside `onCreate()` + +```kotlin +lateinit var flagsmith : Flagsmith + +override fun onCreate(savedInstanceState: Bundle?) { + initFlagsmith(); +} + +private fun initFlagsmith() { + flagsmith = Flagsmith(environmentKey = FlagsmithConfigHelper.environmentDevelopmentKey, context = context) +} +``` + +## Custom configuration + +The Flagsmith SDK has various parameters for initialisation. Most of these are optional, and allow you to configure the +Flagsmith SDK to your specific needs: + +- `environmentKey` Take this API key from the Flagsmith dashboard and pass here +- `baseUrl` By default we'll connect to the Flagsmith backend, but if you self-host you can configure here +- `context` The current Context is required to use the Flagsmith Analytics functionality +- `enableAnalytics` Enable analytics - default true. Disable this if you'd like to avoid the use of Context +- `analyticsFlushPeriod` The period in seconds between attempts by the Flagsmith SDK to push analytic events to the + server +- `enableRealtimeUpdates` Enable the SDK to receive updates to features in real time while the app is running +- `defaultFlags` Provide default flags the the SDK to ensure values are availble when no network connection can be made +- `cacheConfig` Disabled by default, but when enabled will allow Flagsmith to fall back to cached values when no network + connection can be made +- `request / read / writeTimeoutSeconds` Fine-grained control of the HTTP timeouts used inside the Flagsmith SDK + +## Flags + +Now you are all set to retrieve feature flags from your project. To list and print all flags: + +```kotlin +flagsmith.getFeatureFlags { result -> + result.fold( + onSuccess = { flagList -> + Log.i("Flagsmith", "Current flags:") + flagList.forEach { Log.i("Flagsmith", "- ${it.feature.name} - enabled: ${it.enabled} value: ${it.featureStateValue ?: "not set"}") } + }, + onFailure = { err -> + Log.e("Flagsmith", "Error getting feature flags", err) + }) +} +``` + +### Get Flags for an Identity + +To get feature flags for a specific identity: + +```kotlin +flagsmith.getFeatureFlags(identity = "test-user@gmail.com") { result -> + result.fold( + onSuccess = { flagList -> + Log.i("Flagsmith", "Current flags:") + flagList.forEach { Log.i("Flagsmith", "- ${it.feature.name} - enabled: ${it.enabled} value: ${it.featureStateValue ?: "not set"}") } + }, + onFailure = { err -> + Log.e("Flagsmith", "Error getting feature flags", err) + }) +} +``` + +You can also get flags for an identity and set the traits at the same time: + +```kotlin +flagsmith.getFeatureFlags(identity = "test-user@gmail.com", traits = listOf(Trait(key = "set-from-client", value = "12345"))) { result -> + result.fold( + onSuccess = { flagList -> + Log.i("Flagsmith", "Current flags:") + flagList.forEach { Log.i("Flagsmith", "- ${it.feature.name} - enabled: ${it.enabled} value: ${it.featureStateValue ?: "not set"}") } + }, + onFailure = { err -> + Log.e("Flagsmith", "Error getting feature flags", err) + }) +} +``` + +### Get Flag Object by `featureId` + +To retrieve a feature flag boolean value by its name: + +```kotlin +flagsmith.hasFeatureFlag(forFeatureId = "test_feature1") { result -> + val isEnabled = result.getOrDefault(true) + Log.i("Flagsmith", "test_feature1 is enabled? $isEnabled") +} +``` + +### Create a Trait for a user identity + +```kotlin +flagsmith.setTrait(Trait(key = "set-from-client", value = "12345"), identity = "test@test.com") { result -> + result.fold( + onSuccess = { _ -> + Log.i("Flagsmith", "Successfully set trait") + + }, + onFailure = { err -> + Log.e("Flagsmith", "Error setting trait", err) + }) +} +``` + +### Get all Traits + +To retrieve a trait for a particular identity as explained here +[Traits](../../basic-features/managing-identities.md#identity-traits) + +```kotlin +flagsmith.getTraits(identity = "test@test.com") { result -> + result.fold( + onSuccess = { traits -> + traits.forEach { + Log.i("Flagsmith", "Trait - ${it.key} : ${it.traitValue}") + } + }, + onFailure = { err -> + Log.e("Flagsmith", "Error getting traits", err) + }) +} +``` + +### Providing Default Flags + +You can define default flag values when initialising the SDK. This ensures that your application works as intended in +the event that it cannot receive a response from our API. + +```kotlin +val defaultFlags = listOf( + Flag( + feature = Feature( + id = 345345L, + name = "Flag 1", + createdDate = "2023‐07‐07T09:07:16Z", + description = "Flag 1 description", + type = "CONFIG", + defaultEnabled = true, + initialValue = "true" + ), enabled = true, featureStateValue = "value1" + ), + Flag( + feature = Feature( + id = 34345L, + name = "Flag 2", + createdDate = "2023‐07‐07T09:07:16Z", + description = "Flag 2 description", + type = "CONFIG", + defaultEnabled = true, + initialValue = "true" + ), enabled = true, featureStateValue = "value2" + ), +) + +// Then pass these during initialisation: +flagsmith = Flagsmith( + environmentKey = FlagsmithConfigHelper environmentDevelopmentKey, + defaultFlags = defaultFlags, + context = context) + +``` + +### Cache + +By default, the cache is off. When turned on, Flagsmith will cache all flags returned by the API (to permanent storage), +and in case of a failed response, fall back on the cached values. The cache can be turned off or on during +initialisation: + +```kotlin +flagsmith = Flagsmith( + environmentKey = FlagsmithConfigHelper environmentDevelopmentKey, + cacheConfig = FlagsmithCacheConfig(enableCache = true) + context = context) +``` + +You can also set a TTL for the cache (in seconds) for finer control: + +```kotlin +FlagsmithCacheConfig ( + enableCache = true, + cacheTTLSeconds = 3600L, // 1 hour + val cacheSize = 1024L * 1024L, // 1 MB +) +``` + +## Override the default base URL + +By default, the client uses a default configuration. You can override the configuration as follows. If you're also using +realtime flag updates in your hosted environment you'll also need to pass the eventSourceUrl in a similar fashion: + +```kotlin + flagsmith = Flagsmith( + environmentKey = Helper.environmentDevelopmentKey, + context = context, + baseUrl = "https://flagsmith.example.com/api/v1/"), + eventSourceUrl = "https://realtime.flagsmith.example.com/" +``` diff --git a/docs/docs/flagsmith-integration/client-side-sdks/flutter.md b/docs/docs/flagsmith-integration/client-side-sdks/flutter.md new file mode 100644 index 000000000000..2282a0f560cf --- /dev/null +++ b/docs/docs/flagsmith-integration/client-side-sdks/flutter.md @@ -0,0 +1,290 @@ +--- +title: Flagsmith Flutter SDK +sidebar_label: Flutter +description: Manage your Feature Flags and Remote Config in your Flutter Applications. +slug: /clients/flutter +--- + +import CodeBlock from '@theme/CodeBlock'; import { FlutterVersion } from '@site/src/components/SdkVersions.js'; + +This SDK can be used for Flutter applications. The source code for the client is available on +[GitHub](https://github.com/flagsmith/flagsmith-flutter-client). + +The Flagsmith Flutter SDK supports iOS, Android and Web targets. + +## Getting Started + +Install the [client library](https://pub.dev/packages/flagsmith) by adding it to your application's pubspec.yaml file: + + +{`dependencies: + flagsmith: ^`} + + +## Basic Usage + +The SDK is initialised against a single Environment. You can find your Environment key in the Environment settings page. + +### Retrieving feature flags for your project + +In your application, initialise the Flagsmith client with your API key: + +```dart +import 'package:flagsmith/flagsmith.dart'; + +final flagsmithClient = FlagsmithClient( + apiKey: 'YOUR_ENV_API_KEY' + config: config, + seeds: [ + Flag.seed('feature', enabled: true), + ], + ); +await flagsmithClient.initialize(); +await flagsmithClient.getFeatureFlags(reload: true) // fetch updates from api +``` + +If you prefer async initialization then you can use: + +```dart +import 'package:flagsmith/flagsmith.dart'; + +final flagsmithClient = await FlagsmithClient.init( + apiKey: 'YOUR_ENV_API_KEY', + config: config, + seeds: [ + Flag.seed('feature', enabled: true), + ], + ); +await flagsmithClient.getFeatureFlags(reload: true) // fetch updates from api +``` + +To check if a feature flag exists: + +```dart +bool featureExists = await flagsmithClient.hasFeatureFlag("my_test_feature"); +``` + +Check if a feature flag exist and is enabled: + +```dart +bool featureEnabled = await flagsmithClient.isFeatureFlagEnabled("my_test_feature"); +if (featureEnabled) { + // run the code to execute enabled feature +} else { + // run the code if feature switched off +} +``` + +To get the configuration value for a feature flag: + +```dart +final myRemoteConfig = await flagsmithClient.getFeatureFlagValue("my_test_feature"); +if (myRemoteConfig != null) { + // run the code to use remote config value +} else { + // run the code without remote config +} +``` + +To listen for fetch request state: + +```dart +flagsmithClient.loading.listen((state){ + // FlagsmithLoading.loading + // FlagsmithLoading.loaded +}); +``` + +To listen for feature flag changes: + +```dart +flagsmithClient.stream("my_test_feature").listen((value){ + // call to action +}); +``` + +```dart +StreamBuilder( + stream: flagsmithClient.stream("my_test_feature"), + builder: (context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return CircularProgressIndicator(); + } + return TextButton( + onPressed: snapshot.data.enabled ? (){} : null, + child: Text('Call to Action'),); + }, +), +``` + +## Cached flags + +You can use caches instead of async/await: + +```dart +final config = FlagsmithConfig( + baseURI: 'https://flagsmith.example.com/api/v1/', + connectTimeout: 200, + receiveTimeout: 500, + sendTimeout: 500, + storeType = StoreType.inMemory, + caches: true, // mandatory if you want to use caches +); +await flagsmithClient.initialize(); + +final flagsmithClient = await FlagsmithClient.init( + apiKey: 'YOUR_ENV_API_KEY', + config: config, + seeds: [ + Flag.seed('feature', enabled: true), + ], + ); + +await flagsmithClient.getFeatureFlags(reload: true); // fetch updates from api +bool isFeatureEnabled = flagsmithClient.hasCachedFeatureFlag('feature'); +``` + +### Identifying users + +To check if a feature exists for an identity: + +```dart +final user = Identity(identifier: 'flagsmith_sample_user'); +bool featureEnabled = await flagsmithClient.hasFeatureFlag('my_test_feature', user: user); +if (featureEnabled) { + // run the code to execute enabled feature for given user +} else { + // run the code when feature switched off +} +``` + +To get the feature flag configuration value for an identity: + +```dart +final myRemoteConfig = await flagsmithClient.getFeatureFlagValue('my_test_feature', user: user); +if (myRemoteConfig != null) { + // run the code to use remote config value +} else { + // run the code without remote config +} +``` + +To get the user traits for an identity: + +```dart +final userTraits = await flagsmithClient.getTraits(user) +if (userTraits != null && userTraits) { + // run the code to use user traits +} else { + // run the code without user traits +} +``` + +To get the trait value for an identity and specific trait key: + +```dart +final userTrait = await flagsmithClient.getTrait(user, 'cookies_key'); +if (userTrait != null) { + // run the code to use user trait +} else { + // run the code without user trait +} +``` + +Or get user traits for an identity and specific trait keys: + +```dart +final userTraits = await flagsmithClient.getTraits(user, keys: ['cookies_key', 'other_trait']); +if (userTraits != null) { + // run the code to use user traits +} else { + // run the code without user traits +} +``` + +To update a user trait for an identity: + +```dart +final userTrait = await flagsmithClient.getTrait(user, 'cookies_key'); +if (userTrait != null) { + // update value for user trait + var updatedTrait = userTrait.copyWith(value: 'new value'); + Trait updated = await flagsmithClient.updateTrait(user, updatedTrait); +} else { + // run the code without user trait +} +``` + +## Reset storage + +To reset storage and re-seed default values: + +```dart +await flagsmithClient.reset(); +``` + +## Override default configuration + +By default, the client uses the default configuration. You can override this configuration as follows: + +```dart +final flagsmithClient = FlagsmithClient( + config: FlagsmithConfig( + baseURI: 'https://flagsmith.example.com/api/v1/' + ), apiKey: 'YOUR_ENV_API_KEY'); +``` + +Override the default configuration with your own: + +```dart +final flagsmithClient = FlagsmithClient( + config: FlagsmithConfig( + baseURI: 'https://flagsmith.example.com/api/v1/', + connectTimeout: 200, + receiveTimeout: 500, + sendTimeout: 500, + storeType = StoreType.inMemory, + caches: true, + ), apiKey: 'YOUR_ENV_API_KEY'); +``` + +## Real-time Flag Updates + +:::tip + +Real-time Flags are part of our SaaS Scale-Up and Enterprise plans. + +Real-time Flags are currently in beta. Please contact us to join the beta! + +::: + +Real-time flag updates are disabled by default. You can enable them simply by changing the configuration as follows: + +```dart +final flagsmithClient = FlagsmithClient( + config: FlagsmithConfig( + enableRealtimeUpdates: true, + ), apiKey: 'YOUR_ENV_API_KEY'); +``` + +This will use the default Flagsmith realtime updates URI: `'https://realtime.flagsmith.com/sse/environments/'`, with a +reconnect interval of 29000 milliseconds. + +You can change this configuration with your own SSE connection: + +```dart +final flagsmithClient = FlagsmithClient( + config: FlagsmithConfig( + enableRealtimeUpdates: true, + realtimeUpdatesBaseURI: 'https://your_sse_endpoint.com/sse/', + reconnctToSSEInterval: 15000, + ), apiKey: 'YOUR_ENV_API_KEY'); +``` + +## Known issues + +- If using the package Dio, you may encounter an error saying `Bad state: Future already completed`. There is a bug in + the Dio package, introduced in 4.0.5 (and as of writing this on 12/09/2022, is unresolved). To resolve, you'll need to + pin your Dio version in pubspec.yaml to 4.0.4 or earlier. You can track the state of this issue + [here within our repo](https://github.com/Flagsmith/flagsmith-flutter-client/issues/45) and also at the + [dio repo](https://github.com/flutterchina/dio/pull/1550). diff --git a/docs/docs/flagsmith-integration/client-side-sdks/ios.mdx b/docs/docs/flagsmith-integration/client-side-sdks/ios.mdx new file mode 100644 index 000000000000..039dc28ef178 --- /dev/null +++ b/docs/docs/flagsmith-integration/client-side-sdks/ios.mdx @@ -0,0 +1,347 @@ +--- +title: Flagsmith iOS SDK +sidebar_label: iOS / Swift +description: Manage your Feature Flags and Remote Config in your iOS applications. +slug: /clients/ios +--- + +import CodeBlock from '@theme/CodeBlock'; +import { CocoapodsVersion, SwiftPMVersion } from '@site/src/components/SdkVersions.js'; + +This library can be used with iOS and Mac applications. The source code for the client is available on +[GitHub](https://github.com/flagsmith/flagsmith-ios-client). + +## Installation + +
+CocoaPods + +Add the Flagsmith SDK as a dependency to your Podfile: + + + {`pod 'FlagsmithClient', '~> `} + + {`'`} + + +
+ +
+ +Swift Package Manager + +Add the Flagsmith SDK as a dependency to your Package.swift file: + + + {`dependencies: [ + .package(url: "https://github.com/Flagsmith/flagsmith-ios-client.git", from: "`} + + {`"), +]`} + + +Alternatively, you can add the Flagsmith SDK as a dependency from its repository URL using Xcode: + +``` +https://github.com/Flagsmith/flagsmith-ios-client.git +``` + +
+ +## Basic Usage + +The SDK is initialised against a single environment within a project on [https://flagsmith.com](https://flagsmith.com), +for example the Development or Production environment. You can find your Client-side Environment Key in the Environment +settings page. + +### Initialization + +Within your application delegate (usually _AppDelegate.swift_) add: + +```swift +import FlagsmithClient +``` + +```swift +func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + +Flagsmith.shared.apiKey = "" +// The rest of your launch method code +} +``` + +Now you are all set to retrieve feature flags from your project. For example to list and print all flags: + +```swift +Flagsmith.shared.getFeatureFlags() { (result) in + switch result { + case .success(let flags): + for flag in flags { + let name = flag.feature.name + let value = flag.value?.stringValue + let enabled = flag.enabled + print(name, "= enabled:", enabled, "value:", value ?? "nil") + } + case .failure(let error): + print(error) + } +} +``` + +Note that you can use: + +- `flag.value?.stringValue` +- `flag.value?.intValue` + +Based on your desired type. + +To retrieve a feature flag boolean value by its name: + +```swift +Flagsmith.shared.hasFeatureFlag(withID: "test_feature1", forIdentity: nil) { (result) in + print(result) +} +``` + +To retrieve a config value by its name: + +```swift +Flagsmith.shared.getFeatureValue(withID: "test_feature2", forIdentity: nil) { (result) in + switch result { + case .success(let value): + print(value ?? "nil") + case .failure(let error): + print(error) + } +} +``` + +These methods can also specify a particular identity to retrieve the values for a user registration. See +[Identities](/basic-features/managing-identities/) , using the **forIdentity** parameter. + +To retrieve a trait for a particular identity (see [Traits](/basic-features/managing-identities#identity-traits)): + +```swift +Flagsmith.shared.getTraits(forIdentity: "test_user@test.com") {(result) in + switch result { + case .success(let traits): + for trait in traits { + let name = trait.key + let value = trait.value + print(name, "=", value) + } + case .failure(let error): + print(error) + } +} +``` + +To retrieve a flag for a particular identity: + +```swift +Flagsmith.shared.getFeatureFlags(forIdentity: "test_user@test.com") {(result) in + switch result { + case .success(let flags): + for flag in flags { + let name = flag.feature.name + let value = flag.value?.stringValue + let enabled = flag.enabled + print(name, "= enabled:", enabled, "value:", value ?? "nil") + } + case .failure(let error): + print(error) + } +} +``` + +If you would prefer to do this using async/await you can do the following: + +```swift +let flags = try await Flagsmith.shared.getFeatureFlags(forIdentity: "test_user@test.com") +for flag in flags { + let name = flag.feature.name + let value = flag.value?.stringValue + let enabled = flag.enabled + print(name, "= enabled:", enabled, "value:", value ?? "nil") +} +``` + +You can also retrieve flags for a particular identity and set traits at the same time: + +```swift +Flagsmith.shared.getFeatureFlags(forIdentity: "test_user@test.com", traits: [Trait(key: "selected_tint_color", value: "orange")]) {(result) in + switch result { + case .success(let flags): + for flag in flags { + let name = flag.feature.name + let value = flag.value?.stringValue + let enabled = flag.enabled + print(name, "= enabled:", enabled, "value:", value ?? "nil") + } + case .failure(let error): + print(error) + } +} +``` + +If you would prefer to do this using async/await you can do the following: + +```swift +let flags = try await Flagsmith.shared.getFeatureFlags(forIdentity: "test_user@test.com", traits: [Trait(key: "selected_tint_color", value: "orange")]) +for flag in flags { + let name = flag.feature.name + let value = flag.value?.stringValue + let enabled = flag.enabled + print(name, "= enabled:", enabled, "value:", value ?? "nil") +} +``` + +## Override Default Configuration + +In `AppDelegate.swift`: + +```swift +func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + Flagsmith.shared.apiKey = "" + + // set cache on / off (defaults to off) + Flagsmith.shared.cacheConfig.useCache = true + + // set custom cache to use (defaults to shared URLCache) + //Flagsmith.shared.cacheConfig.cache = + + // set skip API on / off (defaults to off) + Flagsmith.shared.cacheConfig.skipAPI = false + + // set cache TTL in seconds (defaults to 0, i.e. infinite) + Flagsmith.shared.cacheConfig.cacheTTL = 90 + + // set analytics on or off + Flagsmith.shared.enableAnalytics = true + + // set the analytics flush period in seconds + Flagsmith.shared.analyticsFlushPeriod = 10 + + Flagsmith.shared.getFeatureFlags() { (result) in + print(result) + } + Flagsmith.shared.hasFeatureFlag(withID: "freeze_delinquent_accounts") { (result) in + print(result) + } + //Flagsmith.shared.setTrait(Trait(key: "", value: ""), forIdentity: "") { (result) in print(result) } + //Flagsmith.shared.getIdentity("") { (result) in print(result) } + return true +} +``` + +## Swift Concurrency + +When running with Swift version 5.5.2 and greater (Xcode 13.2), `async` versions of the Flagsmith API become available. +These are provided using the generic +[`withCheckedThrowingContinuation(function:_:)`](https://developer.apple.com/documentation/swift/3814989-withcheckedthrowingcontinuation) +Swift api, to wrap the closure based syntax. The `async`/`await` syntax provides a streamlined execution flow leading to +greater code clarity. For example: + +```swift +/// (Example) Setup the app based on the available feature flags. +func determineAppConfiguration() async throws { + let flagsmith = Flagsmith.shared + + if try await flagsmith.hasFeatureFlag(withID: "ab_test_enabled") { + if let theme = try await flagsmith.getFeatureValue(withID: "app_theme") { + setTheme(theme) + } else { + let flags = try await flagsmith.getFeatureFlags() + processFlags(flags) + } + } else { + let trait = Trait(key: "selected_tint_color", value: "orange") + let identity = "4DDBFBCA-3B6E-4C59-B107-954F84FD7F6D" + try await flagsmith.setTrait(trait, forIdentity: identity) + } +} +``` + +## Providing Default Flags + +You can define default flag values when initialising the SDK. This ensures that your application works as intended in +the event that it cannot receive a response from our API. + +```swift +// set default flags +Flagsmith.shared.defaultFlags = [Flag(featureName: "feature_a", enabled: false), + Flag(featureName: "font_size", intValue:12, enabled: true), + Flag(featureName: "my_name", stringValue:"Testing", enabled: true)] +``` + +### Cache + +By default, the cache is off. When turned on, Flagsmith will cache all flags returned by the API (to permanent storage), +and in case of a failed response, fall back on the cached values. The cache can be turned off or on using: + +```swift +// set cache on / off (defaults to off) +Flagsmith.shared.cacheConfig.useCache = true +``` + +You can also set a TTL for the cache (in seconds), and request that Flagsmith skip calling the API if a valid cache is +present + +```swift +// set skip API on / off (defaults to off) +Flagsmith.shared.cacheConfig.skipAPI = false + +// set cache TTL in seconds (defaults to 0, i.e. infinite) +Flagsmith.shared.cacheConfig.cacheTTL = 0 +``` + +If more customisation is required, you can override the cache implemention with your own subclass of +[URLCache](https://developer.apple.com/documentation/foundation/urlcache), using the following code. + +```swift +// set custom cache to use (defaults to shared URLCache) +Flagsmith.shared.cacheConfig.cache = +``` + +### Real Time Updates + +By default real-time updates are disabled. When enabled the SDK will listen for changes to flags and update the cache as +needed. If you want to enable real-time updates you can do so by setting the following flag: + +```swift +Flagsmith.shared.enableRealTimeUpdates = false +``` + +It's possible to listen to updates in real time from the SDK using the `flagStream` property. + +```swift +func subscribeToFlagUpdates() { + Task { + for await updatedFlags in flagsmith.flagStream { + DispatchQueue.main.async { + flags = updatedFlags + } + } + } +} +``` + +You can find an example of this functionality in the Flagsmith iOS Example app, which should work on your own data if +you replace your Environment Key in the `AppDelegate.swift` file. + +## Override default configuration + +By default, the client uses a default configuration. You can override the configuration as follows: + +Override just the default API URI with your own: + +```swift +func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + + Flagsmith.shared.apiKey = "" + Flagsmith.shared.baseURL = "https://flagsmith.example.com/api/v1/" + Flagsmith.eventSourceBaseURL = "https://realtime.flagsmith.example.com/" + // The rest of your launch method code +} +``` diff --git a/docs/docs/flagsmith-integration/client-side-sdks/javascript.md b/docs/docs/flagsmith-integration/client-side-sdks/javascript.md new file mode 100644 index 000000000000..4c3f5e8225cf --- /dev/null +++ b/docs/docs/flagsmith-integration/client-side-sdks/javascript.md @@ -0,0 +1,560 @@ +--- +title: Flagsmith JavaScript SDK +sidebar_label: JavaScript +description: Manage your Feature Flags and Remote Config in your JavaScript Applications. +slug: /clients/javascript +--- + +This library can be used with pure JavaScript, React (and all other popular frameworks/libraries) and React Native projects. The source code for the client is available on [GitHub](https://github.com/flagsmith/flagsmith-js-client). + +Example applications for a variety of JavaScript frameworks such as React, Vue and Angular, as well as React Native, can be found here: + +- [Flagsmith Framework Examples](https://github.com/Flagsmith/flagsmith-js-examples/tree/main) + +## Installation + +### NPM + +```bash +npm i flagsmith --save +``` + +### NPM for React Native + +:::tip + +The React Native SDK shares the exact same implementation of Flagsmith, however it requires an implementation of +AsyncStorage to be provided (e.g. @react-native-community/async-storage) in order to utilise analytics and caching. See +[here](/clients/javascript#initialisation-options). + +::: + +```bash +npm i react-native-flagsmith --save +``` + +## Basic Usage + +The SDK is initialised against a single environment within a project on [https://flagsmith.com](https://flagsmith.com), +for example the Development or Production environment. You can find your Client-side Environment Key in the Environment +settings page. + +### Example: Initialising the SDK + +```javascript +import flagsmith from 'flagsmith or react-native-flagsmith'; //Add this line if you're using flagsmith via npm + +flagsmith.init({ + environmentID: '', + // api:"http://localhost:8000/api/v1/" set this if you are self hosting, and point it to your API + cacheFlags: true, // stores flags in localStorage cache + enableAnalytics: true, // See https://docs.flagsmith.com/flag-analytics/ for more info. + onChange: (oldFlags, params) => { + //Occurs whenever flags are changed + const { isFromServer } = params; //determines if the update came from the server or local cached storage + + //Check for a feature + if (flagsmith.hasFeature('my_cool_feature')) { + myCoolFeature(); + } + + //Or, use the value of a feature + const bannerSize = flagsmith.getValue('banner_size'); + + //Check whether value has changed + const bannerSizeOld = oldFlags['banner_size'] && oldFlags['banner_size'].value; + if (bannerSize !== bannerSizeOld) { + // Do something! + } + }, +}); +``` + +:::info + +As of flagsmith 4.0.0, `flagsmith.init` will return a promise resolving with either cache or the latest features or +defaults. The promise will reject if there is no cache and an invalid or no API response was received. + +::: + +### Providing Default Flags + +You can define default flag values when initialising the SDK. This ensures that your application works as intended in +the event that it [cannot receive a response from our API](/guides-and-examples/defensive-coding). + +```javascript +import flagsmith from 'flagsmith or react-native-flagsmith'; //Add this line if you're using flagsmith via npm + +try { + flagsmith.init({ + environmentID: '', + defaultFlags: { + feature_a: { enabled: false}, + font_size: { enabled: true, value: 12 }, + } + onChange: (oldFlags, params) => { + ... + }, + }); +} catch (e) { + // if an exception is thrown the default values will be used +} +``` + +### Default Flag Offline Handler + +You can automatically set default flags for your frontend application as part of your CI/CD process by using our +[CLI](/clients/CLI) and offline hander in your build pipelines. + +The main steps to achieving this are as follows: + +1. Install the [CLI](/clients/CLI) `npm i flagsmith-cli --save-dev` +2. Call the CLI as part of npm postinstall to create a `flagsmith.json` file each time you run `npm install`. This can + be done by either: + + - Using an environment variable `export FLAGSMITH_ENVIRONMENT= flagsmith get` + - Manually specifying your environment key `flagsmith get `. + +3. In your application, initialise Flagsmith with the resulting JSON. This will set default flags before attempting to + use local storage or call the API. `flagsmith.init({environmentID: json.environmentID, state:json})` + +A working example of this can be found [here](https://github.com/Flagsmith/flagsmith-cli/tree/main/example). A list of +cli commands can be found [here](https://github.com/Flagsmith/flagsmith-cli). + +## Identifying users + +Identifying users allows you to target specific users from the Flagsmith dashboard and configure features and traits. +You can call this before or after you initialise the project, calling it after will re-fetch feature flags from the API. + +You can identify the users as part of initialising the client or after with the function `flagsmith.identify`. + +User features can be managed by navigating to users on [https://flagsmith.com](https://flagsmith.com) for your desired +project. ![Image](/img/user-features.png) + +### Example: Identifying a user after initialising the client + +When you initialise the client without an identity, it will fetch the flags for a given environment (unless you provide +`preventFetch:true`). + +```javascript +import flagsmith from 'flagsmith'; + +flagsmith.init({ + environmentID: '', + onChange: (oldFlags, params) => { + //Occurs whenever flags are changed + + const { isFromServer } = params; //determines if the update came from the server or local cached storage + + //Set a trait against the identity + flagsmith.setTrait('favourite_colour', 'blue'); //This save the trait against the user, it can be queried with flagsmith.getTrait + + //Check for a feature + if (flagsmith.hasFeature('my_power_user_feature')) { + myPowerUserFeature(); + } + + //Check for a trait + if (!flagsmith.getTrait('accepted_cookie_policy')) { + showCookiePolicy(); + } + + //Or, use the value of a feature + const myPowerUserFeature = flagsmith.getValue('my_power_user_feature'); + + //Check whether value has changed + const myPowerUserFeatureOld = oldFlags['my_power_user_feature'] && oldFlags['my_power_user_feature'].value; + if (myPowerUserFeature !== myPowerUserFeatureOld) { + // Do something! + } + }, +}); + +/* +Can be called either after you're done initialising the project or in flagsmith.init with its identity and trait properties +to prevent flags being fetched twice. +*/ +flagsmith.identify('flagsmith_sample_user'); //This will create a user in the dashboard if they don't already exist +``` + +### Example: Initialising the SDK with a user + +Initialising the client with an identity property will retrieve the user's flags instead of the environment defaults. +You can also specify traits at this point which could determine the flags that come back based on segment overrides. + +```javascript +import flagsmith from 'flagsmith'; + +flagsmith.init({ + environmentID: '', + identity: 'flagsmith_sample_user', + traits: { age: 21, country: 'England' }, // these will add to the user's existing traits + onChange: (oldFlags, params) => { + //Occurs whenever flags are changed + + const { isFromServer } = params; //determines if the update came from the server or local cached storage + + //Set a trait against the identity + flagsmith.setTrait('favourite_colour', 'blue'); //This save the trait against the user, it can be queried with flagsmith.getTrait + + //Check for a feature + if (flagsmith.hasFeature('my_power_user_feature')) { + myPowerUserFeature(); + } + + //Check for a trait + if (!flagsmith.getTrait('accepted_cookie_policy')) { + showCookiePolicy(); + } + + //Or, use the value of a feature + const myPowerUserFeature = flagsmith.getValue('my_power_user_feature'); + + //Check whether value has changed + const myPowerUserFeatureOld = oldFlags['my_power_user_feature'] && oldFlags['my_power_user_feature'].value; + if (myPowerUserFeature !== myPowerUserFeatureOld) { + } + }, +}); +``` + +## API Reference + +All function and property types can be seen +[here](https://github.com/Flagsmith/flagsmith-js-client/blob/main/types.d.ts#L35). + +### Initialisation options + +| Property | Description | Required | Default Value | +| ------------------------------------------------------------------------------------------- | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -------: | ----------------------------------------------------: | +| `environmentID: string` | Defines which project environment you wish to get flags for. _example ACME project - Staging._ | **YES** | null | +| `onChange?: (previousFlags:IFlags, params:IRetrieveInfo, loadingState:LoadingState)=> void` | Your callback function for when the flags are retrieved `(previousFlags,{isFromServer:true/false,flagsChanged: true/false, traitsChanged:true/false})=>{...}` | **YES** | null | +| `onError?: (res:{message:string}) => void` | Callback function on failure to retrieve flags. `(error)=>{...}` | | null | +| `realtime?:boolean` | Whether to listen for [Real Time Flag events](/advanced-use/real-time-flags) | | false | +| `AsyncStorage?:any` | Needed in certain frameworks cacheFlags and enableAnalytics options, used to tell the library what implementation of AsyncStorage your app uses, e.g. @react-native-community/async-storage, for web this defaults to an internal implementation. | | built in implementation for web, otherwise undefined. | +| `cacheFlags?: boolean` | Any time flags are retrieved they will be cached, flags and identities will then be retrieved from local storage before hitting the API (see cache options). Requires AsyncStorage to be accessible. | | null | +| `cacheOptions?: \{ttl?:number, skipAPI?:boolean, loadStale?:boolean\}` | A ttl in ms (default to 0 which means infinite) and option to skip hitting the API in flagsmith.init if there's cache available. Setting `loadStale: true` will still use cached values regardless of skipping the API. | | \{ttl:0, skipAPI:false, loadStale: false\} | +| `enableAnalytics?: boolean` | [Enable sending flag analytics](/advanced-use/flag-analytics.md) for getValue and hasFeature evaluations. | | false | +| `enableLogs?: boolean` | Enables logging for key Flagsmith events | | null | +| `defaultFlags?: {flag_name: {enabled: boolean, value: string,number,boolean}}` | Allows you define default features, these will all be overridden on first retrieval of features. | | null | +| `preventFetch?: boolean` | If you want to disable fetching flags and call getFlags later. | | false | +| `state?: IState` | Set a predefined state, useful for SSR / isomorphic applications. | | false | +| `api?: string` | Use this property to define where you're getting feature flags from, e.g. if you're self hosting. | | https://edge.api.flagsmith.com/api/v1/ | +| `eventSourceUrl?: string` | Use this property to define where you're getting real-time flag update events (server sent events) from, e.g. if you're self hosting. | | https://edge.api.flagsmith.com/api/v1/ | +| `identity?: string` | Specifying an identity will fetch flags for that identity in the initial API call. | **YES** | null | +| `traits?:Record` | Specifying traits will send the traits for that identity in the initial API call. | **YES** | null | + +### Available Functions + +| Property | Description | +| --------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| init(initialisationOptions)=> Promise<void> | Initialise the sdk against a particular environment | +| hasFeature(key:string)=> boolean | Get the value of a particular feature e.g. `flagsmith.hasFeature("powerUserFeature") // true` | +| getValue\(key:string,\{ json?:boolean, fallback?:T \})=> string|number|boolean | Get the value of a particular feature e.g. `flagsmith.getValue("font_size", { fallback: 12 }) // 10`, specifying json:true will automatically parse the value as JSON. | +| getTrait(key:string)=> string|number|boolean | Once used with an identified user you can get the value of any trait that is set for them e.g. `flagsmith.getTrait("accepted_cookie_policy")` | +| getAllTraits()=> Record<string,string|number|boolean> | Once used with an identified user you can get a key value pair of all traits that are set for them e.g. `flagsmith.getTraits()` | +| getState()=>IState | Retrieves the current state of flagsmith, useful in NextJS / isomorphic applications. `flagsmith.getState()` | +| setState(state: IState)=>void | Set the current state of flagsmith, [useful in NextJS / isomorphic applications.](/clients/next-ssr#comparing-ssr-and-client-side-flagsmith-usage) e.g. `flagsmith.setState({identity: 'mary@mycompany.com'})`. | +| setTrait(key:string, value:string|number|boolean)=> Promise<IFlags> | Once used with an identified user you can set the value of any trait relevant to them e.g. `flagsmith.setTrait("accepted_cookie_policy", true)` | +| setTraits(values:Record\)=\> Promise<IFlags> | Set multiple traits e.g. `flagsmith.setTraits({foo:"bar",numericProp:1,boolProp:true})`. Setting a value of null for a trait will remove that trait. | +| incrementTrait(key:string, value:number)=> Promise<IFlags> | You can also increment/decrement a particular trait them e.g. `flagsmith.incrementTrait("click_count", 1)` | +| startListening(ticks=1000:number)=>void | Poll the api for changes every x milliseconds | +| stopListening()=>void | Stop polling the api | +| getFlags()=> Promise<IFlags> | Trigger a manual fetch of the environment features, if a user is identified it will fetch their features. Resolves a promise when the flags are updated. | +| getAllFlags()=> <IFlags> | Returns the current flags. | +| identify(userId:string, traits?:Record\)=> Promise<IFlags> | Identify as a user, optionally with traits e.g. `{foo:"bar",numericProp:1,boolProp:true}`. This will create a user for your environment in the dashboard if they don't exist, it will also trigger a call to `getFlags()`, resolves a promise when the flags are updated. | +| logout()=>Promise<IFlags> | Stop identifying as a user, this will trigger a call to `getFlags()` | + +## Multiple SDK Instances + +[Version 1.5 and above](https://github.com/Flagsmith/flagsmith-js-client/releases/tag/1.5.0) allows you to create +multiple instances of the Flagsmith SDK. This may be used when you wish to identify multiple users simultaneously within +your app and retain access to getValue, hasFeature etc for each user. + +Type: + +```javascript +export function createFlagsmithInstance (): IFlagsmith +``` + +Usage: + +```javascript +import { createFlagsmithInstance } from 'flagsmith'; +const flagsmith = createFlagsmithInstance(); +const flagsmithB = createFlagsmithInstance(); + +// now you can use flagsmith as before but in its own instance +``` + +## Flagsmith Loading State + +[Version 3.19 and above](https://github.com/Flagsmith/flagsmith-js-client/releases/tag/3.19.0) allows you to determine +the current loading state of the SDK and whether its current data is from default flags, cache or the API. Flagsmith +loading state can be accessed via the onChange event and the +[`useFlagsmithLoading()`](react#useflagsmithloading-api-reference) hook in the React SDK. The expected type of Flagsmith +loading state is as follows: + +```typescript +export declare enum FlagSource { + 'NONE' = 'NONE', + 'DEFAULT_FLAGS' = 'DEFAULT_FLAGS', + 'CACHE' = 'CACHE', + 'SERVER' = 'SERVER', +} + +export declare type LoadingState = { + error: Error | null; // Current error, resets on next attempt to fetch flags + isFetching: bool; // Whether there is a current request to fetch server flags + isLoading: bool; // Whether any flag data exists + source: FlagSource; //Indicates freshness of flags +}; +``` + +## JSON Feature Values + +The Flagsmith JavaScript client supports JSON remote config / feature values. When calling `flagsmith.getValue`, +specifying `json:true` will attempt to parse the feature value as JSON, it will fallback to `fallback` failing to parse +it. + +```javascript +const json = flagsmith.getValue('json_value', { + json: true, + fallback: { foo: null, bar: null }, +}); +``` + +## TypeScript Support + +Flagsmith has full TypeScript support for its JavaScript clients, you can find our main type definition file +[here](https://github.com/Flagsmith/flagsmith-js-client/blob/main/types.d.ts#L35). You can also enforce type safety of +feature and trait names using generics: + +Given that we type our flags and traits: + +```typescript +type FlagOptions = 'font_size' | 'hero'; +type TraitOptions = 'example_trait'; +``` + +We can now enforce these types: + +```typescript +// enforces you passing the correct key to flagsmith.getValue(flag:FlagOptions), flagsmith.getTrait(trait:TraitOptions) +import flagsmith from 'flagsmith'; +const typedFlagsmith = flagsmith as IFlagsmith; + +// Similarly for the useFlagsmith hook is typed with useFlagsmith(flags:FlagOptions[],traits:TraitOptions[]) +const flagsmith = useFlagsmith(); // enforces flagsmith.getValue() + +// for useFlags this will ensure you only can pass correct keys also +const flags = useFlags(['font_size'], ['example_trait']); + +// for getting JSON values this will type the return +const json = flagsmith.getValue<{ foo: string | null; bar: string | null }>('json_value', { + json: true, + fallback: { foo: null, bar: null }, +}); +console.log(json.foo); // typed as {foo: string|null, bar: string|null} + +// If a type is not specified for getValue it will asume it from the type of fallback. In this case, a number. +const font_size = flagsmith.getValue('font_size', { fallback: 12 }); +``` + +## Datadog RUM JavaScript SDK Integration + +:::caution + +This feature is still in beta with Datadog. Contact your Datadog representative before enabling the integration below. + +::: + +The Flagsmith JavaScript SDK can be configured so that feature enabled state and remote config can be stored as +[Datadog RUM feature flags](https://docs.datadoghq.com/real_user_monitoring/guide/setup-feature-flag-data-collection/?tab=npm#analyze-your-feature-flag-performance-in-rum) +and user traits can be stored as +[Datadog user session properties](https://docs.datadoghq.com/real_user_monitoring/browser/modifying_data_and_context/?tab=npm#addoverride-user-session-property). +The integration requires an initialised Datadog `datadogRum` client. + +### Step 1: Initialise your Datadog RUM SDK with the feature_flags experimental feature + +To start collecting feature flag data, initialize the Datadog RUM SDK and configure the enableExperimentalFeatures +initialization parameter with ["feature_flags"]. + +```typescript +import { datadogRum } from '@datadog/browser-rum'; + +// Initialize Datadog Browser SDK +datadogRum.init({ + enableExperimentalFeatures: ["feature_flags"], + ... +}); +``` + +### Step 2: Initialise the Flagsmith SDK with configuring + +Initialise the Flagsmith SDK with the datadogRum option. Optionally, you can configure the client so that Flagsmith +traits are sent to Datadog via ``datadogRum.setUser()````. + +```typescript +import { datadogRum } from '@datadog/browser-rum'; +... +// Initialize the Flagsmith SDK +flagsmith.init({ + datadogRum: { + client: datadogRum, + trackTraits: true, + }, + ... +}) +``` + +### Step 3: What happens next + +- Whenever flag values are _evaluated_ in your code, they will be sent to Datadog as user events. +- If the option to send Traits is enabled, the Trait key/value pairs will be sent to Datadog when the SDK receives its + Flags. + +This will track remote config and feature enabled states as feature flags in the following format + +```bash +flagsmith_value_ // remote config +flagsmith_enabled_ // enabled state +``` + +Additionally, the integration will also store Flagsmith traits against the Datadog user in the following format: + +```bash +flagsmith_trait_ // remote config +``` + +You can find an example of this integration +[here](https://github.com/Flagsmith/flagsmith-js-examples/blob/main/datadog-realtime-user-monitoring/src/index.tsx). + +## Dynatrace JavaScript SDK Integration + +The Flagsmith JavaScript SDK can be configured so that feature enabled state, remote config and user traits can be +stored as Dynatrace session properties. The integration requires a configured Dynatrace `dtrum` object that is already +set up. + +### Step 1: Pass `enableDynatrace` into `flagsmith.init()` + +In order to configure the JavaScript SDK, you need to pass in an instance of +[dtrum](https://www.dynatrace.com/support/help/how-to-use-dynatrace/real-user-monitoring/basic-concepts/js-tag-api). + +```javascript +// Initialize the Flagsmith SDK +flagsmith.init({ + //...Initialisation properties, + enableDynatrace: true, +}); +``` + +When setting `enableDynatrace` to true `flagsmith.init`, Flagsmith will send session properties corresponding to flag +enabled state, flag values and user traits via +[dtrum.sendSessionProperties()](https://www.dynatrace.com/support/doc/javascriptapi/interfaces/dtrum_types.DtrumApi.html#sendSessionProperties) + +- flag enabled state sends as a **shortString** as `true` or `false` with the prefix `flagsmith_enabled_` + - example: `flagsmith_enabled_hero: "true"` +- Remote config values sends as value with the prefix flagsmith _value_, this value will be a **javaDouble** for numeric + values and a **shortString** for any other. + - example: `flagsmith_value_font_size: 21`, `flagsmith_value_hero_colour: "blue"` +- Remote config values sends as value with the prefix flagsmith _value_, this value will be a **javaDouble** for numeric + values and a **shortString** for any other. + - example: `flagsmith_trait_age: 21`, `flagsmith_trait_favourite_colour: "blue"` + +### Step 2: Add the session properties to your Dynatrace application settings + +[As with any other Dynatrace session properties](https://www.dynatrace.com/support/help/how-to-use-dynatrace/real-user-monitoring/setup-and-configuration/web-applications/additional-configuration/define-user-action-and-session-properties), +you need to also define session properties within the RUM application settings. + +You can also add these properties via the +[Dynatrace API](https://www.dynatrace.com/support/help/dynatrace-api/configuration-api/rum/mobile-custom-app-configuration/user-action-and-session-properties/post-property). + +### Dynatrace Screenshots + +Defining Dynatrace Properties: + +![Image](/img/dynatrace_1.png) + +Defining a Flagsmith and Dynatrace Property: + +![Image](/img/dynatrace_2.png) + +Filtering on a Flagsmith Flag: + +![Image](/img/dynatrace_3.png) + +## FAQs + +**How do I call `identify`, `setTraits` etc alongside `init`?** + +- `init` should be called once in your application, we recommend you call `init` before any other flagsmith call. +- `init` retrieves flags by default, you can turn this off with the `preventFetch` option to `init`. This is useful for + when you know you're identifying a user straight after. + +**When does onChange callback?** + +`onChange` calls when flags are fetched this can be a result of: + +- init +- setTrait +- incrementTrait +- getFlags +- identify +- flags evaluated by local storage + +Using onChange is best used in combination with your application's state management e.g. onChange triggering an action +to re-evaluate flags with `hasFeature` and `getValue`. + +However, if this does not fit in with your development pattern, all the above flagsmith functions return a promise that +resolves when fresh flags have been retrieved. + +For example by doing the following: + +```javascript +await flagsmith.setTrait('age', 21); +const hasFeature = flagsmith.hasFeature('my_feature'); +``` + +On change calls back with information telling you what has changed, you can use this to prevent any unnecessary +re-renders. + +```javascript +onChange(oldFlags, { + isFromServer: true, // flags have come from the server or local storage + flagsChanged: string[] | null, + traitsChanged: string[] | null, +}, loadingState) +``` + +:::info + +Prior to `flagsmith 4.0.0`, flagsChanged and traitsChanged returned a boolean. + +::: + +**How does caching flags work?** + +If the `cacheFlags` is set to true on `init`, the SDK will cache flag evaluations in local async storage. Upon reloading +the browser, an onChange event will be fired immediately with the local storage flags. The flow for this is as follows + +1. `init` is called + +2. if `cacheFlags` is enabled, local storage checks for any stored flags and traits. + +3. if flags have been found in local storage, `onChange` is triggered with the stored flags. + +4. at the same time, fresh flags will be retrieved which will result in another `onChange` callback. + +5. whenever flags have been retrieved local storage will be updated. + +By default, these flags will be persisted indefinitely, you can clear this by removing `"FLAGSMITH_DB_$ENVIRONMENT_ID"` +from `localStorage`. + +**Why am I seeing `ReferenceError: XMLHttpRequest is not defined`?** + +The Flagsmith JavaScript client uses the [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) +API to handle REST calls. Some frameworks such as Manifest and Nuxt do not support this out of the box. + +In order to resolve this issue, you can provide a custom fetch implementation to the Flagsmith SDK. An example of this +can be found [here](https://github.com/Flagsmith/flagsmith-js-examples/blob/main/nuxt/plugins/flagsmith-plugin.ts#L9). diff --git a/docs/docs/flagsmith-integration/client-side-sdks/nextjs-and-ssr.md b/docs/docs/flagsmith-integration/client-side-sdks/nextjs-and-ssr.md new file mode 100644 index 000000000000..cb17580c0917 --- /dev/null +++ b/docs/docs/flagsmith-integration/client-side-sdks/nextjs-and-ssr.md @@ -0,0 +1,226 @@ +--- +title: Flagsmith React SDK +sidebar_label: Next.js and SSR +description: Manage your Feature Flags and Remote Config with NextJS and SSR. +slug: /clients/next-ssr +--- + +The JavaScript Library contains a bundled isomorphic library, allowing you to fetch flags in the server and hydrate your +application with the resulting state. + +Example applications for a variety of Next.js and SSR can be found +[here](https://github.com/flagsmith/flagsmith-js-examples/tree/main/nextjs). + +Example application using the Next.js app router can be found +[here](https://github.com/Flagsmith/flagsmith-js-examples/tree/main/nextjs-approuter). + +Example applications for Svelte be found [here](https://github.com/flagsmith/flagsmith-js-examples/tree/main/svelte). + +An example application for Next.js middleware can be found +[here](https://github.com/flagsmith/flagsmith-js-examples/tree/main/nextjs-middleware). + +## Installation + +### NPM + +```bash +npm i flagsmith --save +``` + +## Basic Usage + +The SDK is initialised against a single environment. You can find your Client-side Environment Key in the Environment +settings page. + +## Comparing SSR and client-side Flagsmith usage + +The SDK is initialised and used in the same way as the [JavaScript](/clients/javascript) and [React](/clients/react) +SDK. The main difference is that Flagsmith should be imported from `flagsmith/isomorphic`. + +The main flow with Next.js and any JavaScript-based SSR can be as follows: + +1. Fetch the flags on the server, optionally passing an identity to + [`flagsmith.init({})`](/clients/javascript#initialisation-options) +2. Pass the resulting state to the client with [`flagsmith.getState()`](/clients/javascript#available-functions) +3. Initialise flagsmith on the client with [`flagsmith.setState(state)`](/clients/javascript#available-functions) + +### Example: Initialising the SDK with Next.js + +Taking the above into account, the following examples fetch flags on the server and initialises Flagsmith with the +state. Below is an example for the **app** router as well as the **pages** router. + +#### App Router Example + +```javascript +// src/app/components/FeatureFlagProvider.tsx +"use client"; + +import { ReactNode, useRef } from "react"; + +import { FlagsmithProvider } from "flagsmith/react"; +import { IState } from "flagsmith/types"; +import { createFlagsmithInstance } from "flagsmith/isomorphic"; + +export const FeatureFlagProvider = ({ + serverState, + children, +}: { + serverState: IState; + children: ReactNode; +}) => { + const flagsmithInstance = useRef(createFlagsmithInstance()); + return ( + + <>{children} + + ); +}; + + +// src/app/layout.jsx +import { ReactNode } from "react"; +import { FeatureFlagProvider } from './components/FeatureFlagProvider'; +import flagsmith from "flagsmith/isomorphic"; + +export default async function RootLayout({ + children, +}: Readonly<{ + children: ReactNode; +}>) { + await flagsmith.init({ + environmentID: "", + // Add optional identity, etc. + }); + const serverState = flagsmith.getState(); + + return ( + + + + + + + {children} + + + + ); +} +``` + +#### Pages Router Example + +```javascript +// src/pages/_app.jsx +import { FlagsmithProvider } from 'flagsmith/react'; +import { createFlagsmithInstance } from 'flagsmith/isomorphic'; +function MyApp({ Component, pageProps, flagsmithState }) { + const flagsmithRef = useRef(createFlagsmithInstance()); + return ( + + + + ); +} + +MyApp.getInitialProps = async () => { + const flagsmithSSR = createFlagsmithInstance(); + await flagsmithSSR.init({ + // fetches flags on the server + environmentID: '', + identity: 'my_user_id', // optionaly specify the identity of the user to get their specific flags + }); + return { flagsmithState: flagsmithSSR.getState() }; +}; + +export default MyApp; +``` + +#### Client Component + +```javascript +'use client'; // Only required by the app router version. + +import { useFlags } from 'flagsmith/react'; + +export function MyComponent() { + const flags = useFlags(['font_size'], ['example_trait']); // only causes re-render if specified flag values / traits change + return ( +
+ font_size: {flags.font_size.value} + example_trait: {flags.example_trait} +
+ ); +} +``` + +From this point on, the SDK usage is the same as the [React SDK Guide](/clients/react) + +### Example: Flagsmith with Next.js middleware + +The Flagsmith JS client includes `flagsmith/next-middleware`, it can be used just like the regular library within +Next.js middleware. + +```javascript +// middleware.ts +import { NextResponse } from 'next/server'; +import type { NextRequest } from 'next/server'; +import flagsmith from 'flagsmith/next-middleware'; + +export async function middleware(request: NextRequest) { + const identity = request.cookies.get('user'); + + if (!identity) { + // redirect to homepage + return NextResponse.redirect(new URL('/', request.url)); + } + + await flagsmith.init({ + // Optionally use a server-side key here because this state won't get passed to the client-side. + environmentID: '', + identity, + }); + + // Return a different URL based on a feature flag + if (flagsmith.hasFeature('beta')) { + return NextResponse.redirect(new URL(`/account-v2/`, request.url)); + } + + // Return a different URL based on a remote config + const theme = flagsmith.getValue('colour'); + return NextResponse.redirect(new URL(`/account/${theme}`, request.url)); +} + +export const config = { + matcher: '/login', +}; +``` + +### Example: SSR without Next.js + +The same can be accomplished without using Next.js. + +Step 1: Initialising the SDK and passing the resulting state to the client. + +```javascript +await flagsmith.init({ + // fetches flags on the server + environmentID: '', + identity: 'my_user_id', // optionaly specify the identity of the user to get their specific flags +}); +const state = flagsmith.getState(); // Pass this data to your client +``` + +Step 2: Initialising the SDK on the client. + +```javascript +flagsmith.setState(state); +``` + +Step 3: Optionally force the client to fetch a fresh set of flags + +```javascript +flagsmith.getFlags(); +``` + +From that point the SDK usage is the same as the [JavaScript SDK Guide](/clients/javascript) diff --git a/docs/docs/flagsmith-integration/client-side-sdks/react.md b/docs/docs/flagsmith-integration/client-side-sdks/react.md new file mode 100644 index 000000000000..8238e835c8fd --- /dev/null +++ b/docs/docs/flagsmith-integration/client-side-sdks/react.md @@ -0,0 +1,153 @@ +--- +title: Flagsmith React SDK +sidebar_label: React and React Native +description: Manage your Feature Flags and Remote Config with React and React Native Hooks. +slug: /clients/react +--- + +This library includes React/React Native Hooks allowing you to query individual features and flags that limit +re-renders. + +Example applications for a variety of React, React Native and Next.js can be found here: + +- [Usage with React](https://github.com/Flagsmith/flagsmith-js-examples/tree/main/react) +- [Usage with React Native](https://github.com/Flagsmith/flagsmith-js-examples/tree/main/reactnative) +- [Usage with Next.js](https://github.com/Flagsmith/flagsmith-js-examples/tree/main/nextjs) + +## Installation + +### NPM + +```bash +npm i flagsmith --save +``` + +### NPM for React Native + +:::tip + +The React Native SDK shares the exact same implementation of Flagsmith, however, requires an implementation of +AsyncStorage to be provided (e.g. @react-native-community/async-storage) in order to utilise analytics and caching. See +[here](/clients/javascript#initialisation-options). + +::: + +```bash +npm i react-native-flagsmith --save +``` + +## Basic Usage + +The SDK is initialised against a single environment. You can find your Client-side Environment Key in the Environment +settings page. + +### Step 1: Wrapping your application with Flagsmith Provider + +Wrapping your application with our FlagsmithProvider component provides a React Context throughout your application so +that you can use the hooks `useFlagsmith` and `useFlags`. + +```javascript +import flagsmith from 'flagsmith' +import {FlagsmithProvider} from 'flagsmith/react' + +export function AppRoot() { + ", + }} flagsmith={flagsmith}> + {...} + +}; +``` + +Providing options to the Flagsmith provider will initialise the client, the API reference for these options can be found +[here](/clients/javascript#initialisation-options). + +:::tip Initialising before rendering the FlagsmithProvider + +If you wish to initialise the Flagsmith client before React rendering (e.g. in redux, or SSR) you can do so by calling +[flagsmith.init](/clients/javascript#example-initialising-the-sdk) and provide no options property to the +FlagsmithProvider component. + +::: + +### Step 2: Using useFlags to access feature values and enabled state + +Components that have been wrapped in a FlagsmithProvider will be able to evaluate feature values and enabled state as +well as user traits via the `useFlags` hook. + +```javascript +import { useFlags } from 'flagsmith/react'; + +export function MyComponent() { + const flags = useFlags(['font_size'], ['example_trait']); // only causes re-render if specified flag values / traits change + return ( +
+ font_size: {flags.font_size.value} + example_trait: {flags.example_trait} +
+ ); +} +``` + +## useFlags API Reference + +```javascript +useFlags(requiredFlags:string[], requiredTraits?:string[])=> {[key:string]: IFlagsmithTrait or IFlagsmithFeature} +``` + +You can find the exact definitions of these types +[in the SDK](https://github.com/Flagsmith/flagsmith-js-client/blob/main/types.d.ts). + +## FlagsmithProvider API Reference + +| Property | Description | Required | Default Value | +| ------------------------ | :------------------------------------------------------------------------------------------------------------: | -------: | ------------: | +| `flagsmith: IFlagsmith` | Defines the flagsmith instance that the provider will use. | **YES** | null | +| `options?: ` IInitConfig | Initialisation options to use. If you don't provide this you will have to call flagsmith.init elsewhere. | | null | +| `serverState?: IState` | Used to pass an initial state, in most cases as a result of SSR flagsmith.getState(). See [Next.js and SSR](/) | | null | + +### Step 3: Using useFlagsmith to access the Flagsmith instance + +Components that have been wrapped in a FlagsmithProvider will be able to access the instance of Flagsmith via the +`useFlagsmith` hook. + +```javascript +import React from 'react'; +import { useFlags, useFlagsmith } from 'flagsmith/react'; + +export function MyComponent() { + const flags = useFlags(['font_size'], ['example_trait']); // only causes re-render if specified flag values / traits change + const flagsmith = useFlagsmith(); + const identify = () => { + // This will re-render the component if the user has the trait example_trait or they have a different feature value for font_size + flagsmith.identify('flagsmith_sample_user'); + }; + const logout = () => { + // This will re-render the component if the user has the trait example_trait or they have a different feature value for font_size + flagsmith.logout(); + }; + return ( +
+ font_size: {flags.font_size?.value} + example_trait: {flags.example_trait} + {flagsmith.identity ? : } +
+ ); +} +``` + +## useFlagsmith API Reference + +This allows you to access the SDK instance that is used within the FlagsmithProvider. + +```javascript +useFlagsmith()=> IFlagsmith +``` + +## useFlagsmithLoading API Reference + +This hook allows you to access the [SDK loading state](javascript#flagsmith-loading-state). + +```javascript +useFlagsmithLoading()=> LoadingState +``` diff --git a/docs/docs/flagsmith-integration/flagsmith-api-overview/_category_.json b/docs/docs/flagsmith-integration/flagsmith-api-overview/_category_.json new file mode 100644 index 000000000000..6269dca75138 --- /dev/null +++ b/docs/docs/flagsmith-integration/flagsmith-api-overview/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Flagsmith API Overview", + "position": 60, + "collapsed": true +} diff --git a/docs/docs/flagsmith-integration/flagsmith-api-overview/admin-api/authentication.md b/docs/docs/flagsmith-integration/flagsmith-api-overview/admin-api/authentication.md new file mode 100644 index 000000000000..b3c0fa25b865 --- /dev/null +++ b/docs/docs/flagsmith-integration/flagsmith-api-overview/admin-api/authentication.md @@ -0,0 +1,28 @@ +--- +title: Authentication +sidebar_label: Authentication +--- + +To interact with the Admin API, you need to authenticate your requests using an API Token associated with your Organisation. + +## Generating an API Token + +You can generate an API Token from the **Organisation Settings** page in the Flagsmith dashboard. + +1. Click on your Organisation name in the top navigation panel. +2. Go to the **API Keys** tab. +3. Click **Create API Key**. + +Give your key a descriptive name so you can remember what it's used for. + +## Using the API Token + +Once you have your token, you need to include it in your API requests as an `Authorization` header. The token should be prefixed with `Api-Key`. + +```bash +Authorization: Api-Key +``` + +This token grants access to manage all projects within that organisation, so be sure to keep it secure and never expose it in client-side applications. + +For SaaS customers, the base URL for the Admin API is `https://api.flagsmith.com/`. If you are self-hosting, you will need to use your own API URL. \ No newline at end of file diff --git a/docs/docs/flagsmith-integration/flagsmith-api-overview/admin-api/code-examples.md b/docs/docs/flagsmith-integration/flagsmith-api-overview/admin-api/code-examples.md new file mode 100644 index 000000000000..c961fe7f02a1 --- /dev/null +++ b/docs/docs/flagsmith-integration/flagsmith-api-overview/admin-api/code-examples.md @@ -0,0 +1,21 @@ +--- +title: Code Examples +sidebar_label: Code Examples +--- + +Here is a simple example of how to use the Admin API with `curl` to create a new environment within a project. + +```bash +curl 'https://api.flagsmith.com/api/v1/environments/' \ + -H 'Content-Type: application/json' \ + -H 'Authorization: Api-Key ' \ + --data-binary '{"name":"New Environment","project":""}' +``` + +### Parameters + +- `Authorization`: Your Organisation API Token, prefixed with `Api-Key`. +- `Content-Type`: `application/json` +- `--data-binary`: The JSON payload containing the details of the environment you want to create. You'll need to provide the `name` for the new environment and the `project` ID it belongs to. + +For more complex examples and different languages, please refer to the full code examples in the [original REST API documentation](../../../clients/rest/#code-examples). \ No newline at end of file diff --git a/docs/docs/flagsmith-integration/flagsmith-api-overview/admin-api/index.md b/docs/docs/flagsmith-integration/flagsmith-api-overview/admin-api/index.md new file mode 100644 index 000000000000..c774892215b0 --- /dev/null +++ b/docs/docs/flagsmith-integration/flagsmith-api-overview/admin-api/index.md @@ -0,0 +1,18 @@ +--- +title: Admin API +sidebar_label: Admin API +--- + +The Admin API allows you to programmatically manage your Flagsmith projects, environments, features, segments, and users. Essentially, any action you can perform in the Flagsmith dashboard can also be accomplished via the Admin API. + +This API is designed for automation, integrations, and building custom workflows on top of Flagsmith. + +## API Explorer + +You can explore the full Admin API via Swagger at [https://api.flagsmith.com/api/v1/docs/](https://api.flagsmith.com/api/v1/docs/). You can also get the OpenAPI specification in [JSON](https://api.flagsmith.com/api/v1/docs/?format=.json) or [YAML](https://api.flagsmith.com/api/v1/docs/?format=.yaml) format. + +We also have a [Postman Collection](https://www.postman.com/flagsmith/workspace/flagsmith/overview) that you can use to experiment with the API. + +:::info +Our Admin API has a [Rate Limit](/system-administration/system-limits#admin-api-rate-limit) that you should be aware of. +::: \ No newline at end of file diff --git a/docs/docs/flagsmith-integration/flagsmith-api-overview/flags-api/authentication.md b/docs/docs/flagsmith-integration/flagsmith-api-overview/flags-api/authentication.md new file mode 100644 index 000000000000..ae35aa02eb85 --- /dev/null +++ b/docs/docs/flagsmith-integration/flagsmith-api-overview/flags-api/authentication.md @@ -0,0 +1,24 @@ +--- +title: Authentication +sidebar_label: Authentication +--- + +The Flags API uses a non-secret **Environment Key** for authentication. This key is safe to be exposed in public, client-side applications. + +## Finding Your Environment Key + +You can find the Environment Key for each of your environments in the Flagsmith dashboard. + +1. Navigate to the project you want to work with. +2. Go to the **Environments** tab. +3. You will see a list of your environments, each with its own Client-side Environment Key. + +## Using the Environment Key + +You must supply the Environment Key with each request in an HTTP header named `X-Environment-Key`. + +```bash +X-Environment-Key: +``` + +The SDKs handle this for you automatically when you initialize them with the key. If you are making direct calls to the API, you will need to include this header in every request. \ No newline at end of file diff --git a/docs/docs/flagsmith-integration/flagsmith-api-overview/flags-api/code-examples.md b/docs/docs/flagsmith-integration/flagsmith-api-overview/flags-api/code-examples.md new file mode 100644 index 000000000000..0dffe8287fa9 --- /dev/null +++ b/docs/docs/flagsmith-integration/flagsmith-api-overview/flags-api/code-examples.md @@ -0,0 +1,42 @@ +--- +title: Code Examples +sidebar_label: Code Examples +--- + +Here are some `curl` examples demonstrating how to interact directly with the Flags API. + +### Get Environment Flags + +This command retrieves all the default flag states and remote config values for a specific environment. + +```bash +curl 'https://edge.api.flagsmith.com/api/v1/flags/' \ + -H 'X-Environment-Key: ' +``` + +### Get Flags for an Identified User + +This command performs the entire SDK identity workflow in a single call: + +1. Lazily creates an identity if it doesn't already exist. +2. Sets or updates traits for that identity. +3. Receives the flags for that identity, including any segment or identity-specific overrides. + +```bash +curl --request POST 'https://edge.api.flagsmith.com/api/v1/identities/' \ + --header 'X-Environment-Key: ' \ + --header 'Content-Type: application/json' \ + --data-raw '{ + "identifier":"user_12345", + "traits": [ + { + "trait_key": "subscription_plan", + "trait_value": "premium" + }, + { + "trait_key": "has_beta_access", + "trait_value": true + } + ] + }' +``` \ No newline at end of file diff --git a/docs/docs/flagsmith-integration/flagsmith-api-overview/flags-api/index.md b/docs/docs/flagsmith-integration/flagsmith-api-overview/flags-api/index.md new file mode 100644 index 000000000000..9c2e80b9949c --- /dev/null +++ b/docs/docs/flagsmith-integration/flagsmith-api-overview/flags-api/index.md @@ -0,0 +1,17 @@ +--- +title: Flags API Reference +sidebar_label: Flags API +--- + +The Flags API is the public-facing API that your SDKs use to retrieve feature flags and remote configuration for your users. It's designed for high performance and low latency, with a globally distributed infrastructure to serve requests quickly, wherever your users are. + +This API is used for **reading** flag states and user traits, not for managing your projects. + +## Endpoints + +The two main endpoints you will interact with via the SDKs are: + +- `/flags/`: Get all flags for a given environment. +- `/identities/`: Get all flags and traits for a specific user identity. + +For SaaS customers, the base URL for the Flags API is `https://edge.api.flagsmith.com/`. Our Edge API specification is detailed [here](/edge-api/overview). \ No newline at end of file diff --git a/docs/docs/flagsmith-integration/flagsmith-api-overview/index.md b/docs/docs/flagsmith-integration/flagsmith-api-overview/index.md new file mode 100644 index 000000000000..619e8baea38f --- /dev/null +++ b/docs/docs/flagsmith-integration/flagsmith-api-overview/index.md @@ -0,0 +1,27 @@ +--- +title: Flagsmith API Overview +sidebar_label: Overview +sidebar_position: 10 +--- + +The Flagsmith API is divided into two distinct parts, each serving a different purpose. Understanding the difference is key to integrating with Flagsmith effectively. + +### 1. The Flags API (Public SDK API) + +This is the API that your client and server-side SDKs interact with to get flag and remote configuration values for your environments and users. It's designed to be fast, scalable, and publicly accessible. + +- **Purpose:** Serving flags to your applications. +- **Authentication:** Uses a public, non-secret **Environment Key**. +- **Security:** Open by design. The Environment Key can be exposed in client-side code. + +[Learn more about the Flags API](./flags-api). + +### 2. The Admin API (Private Admin API) + +This is the API you use to programmatically manage your Flagsmith projects. Anything you can do in the Flagsmith dashboard, you can also do via the Admin API. + +- **Purpose:** Creating, updating, and deleting projects, environments, flags, segments, and users. +- **Authentication:** Uses a secret **Organisation API Token**. +- **Security:** Requires a secret key that should never be exposed in client-side code. + +[Learn more about the Admin API](./admin-api). \ No newline at end of file diff --git a/docs/docs/flagsmith-integration/integration-overview.md b/docs/docs/flagsmith-integration/integration-overview.md new file mode 100644 index 000000000000..e156257f6930 --- /dev/null +++ b/docs/docs/flagsmith-integration/integration-overview.md @@ -0,0 +1,54 @@ +--- +title: Integration Overview +sidebar_label: Integration Overview +sidebar_position: 10 +--- + +Flagsmith is designed to be integrated into your applications in a variety of ways, depending on your architecture and requirements. This guide provides an overview of the different integration options available. + +## Deciding between Front-end and Back-end Feature Flags + +One of the first decisions to make when integrating Flagsmith is whether to evaluate flags on the front-end (client-side) or back-end (server-side). + +### Front-end / Client-side + +Client-side SDKs (for browsers, mobile apps, etc.) are great for features that directly impact the user interface, such as showing or hiding a new element, changing button colours, or running A/B tests on UI components. + +- **Pros:** Fast UI updates, easy to implement for UI-related features. +- **Cons:** The Environment Key is public, and segment/targeting rules are not exposed to the client to prevent leaking sensitive information. + +### Back-end / Server-side + +Server-side SDKs run in your trusted back-end environment. They are ideal for controlling deeper application logic, such as enabling a new API endpoint, changing the behaviour of an algorithm, or managing access to certain features based on user permissions that are only known on the server. + +- **Pros:** Secure environment, full access to all targeting rules, can be used to control critical application logic. +- **Cons:** May require an additional API call from the front-end to the back-end to get the flag state if it's needed in the UI. + +You can read more about the differences in our [SDKs Overview documentation](../clients). + +## Identities and Traits + +To get the most out of Flagsmith, you'll want to identify your users. This allows you to: + +- Override flags for specific users. +- Run A/B tests. +- Gradually roll out features to a percentage of your users. +- Target features to specific segments of users. + +An **identity** represents a single user in your application. You can also store **traits** against an identity. Traits are key-value pairs that describe a user, for example, their subscription plan, their location, or how many times they've logged in. + +You can learn more in our documentation on [Managing Identities](../basic-features/managing-identities.md). + +### Transient Traits and Identities + +For privacy-sensitive use cases, you can use transient traits and identities. This allows you to evaluate flags based on user data without persisting that data in Flagsmith. This is useful for things like: + +- Using PII (Personally Identifiable Information) for segmentation without storing it. +- Running experiments on anonymous users. +- Temporarily overriding a trait for a single session. + +Learn more about this feature in our [Transient Traits and Identities documentation](../advanced-use/transient-traits.md). + +## Third-party Integrations + +Flagsmith also integrates with a variety of third-party tools for analytics, project management, and more. You can browse all available integrations in the [Integrations section](../integrations). \ No newline at end of file diff --git a/docs/docs/flagsmith-integration/openfeature.md b/docs/docs/flagsmith-integration/openfeature.md new file mode 100644 index 000000000000..7f1dac3ed74c --- /dev/null +++ b/docs/docs/flagsmith-integration/openfeature.md @@ -0,0 +1,37 @@ +--- +description: OpenFeature +sidebar_label: OpenFeature +sidebar_position: 50 +--- + +# OpenFeature + +[OpenFeature](https://openfeature.dev/) is an open standard for feature flag management, created to support a robust feature flag ecosystem using cloud native technologies. OpenFeature provides a unified API and SDK, and a developer-first, cloud-native implementation, with extensibility for open source and commercial offerings. + +Flagsmith is proud to contribute to this initiative, and is a governance board member of this CNCF project. Our goal, and that of OpenFeature, is to recommend using OpenFeature as the default SDK interface for Flagsmith projects. + +OpenFeature is being actively worked on; we encourage anyone interested in feature flags and open source to get involved! + +## Flagsmith OpenFeature Providers + +We currently offer [OpenFeature Providers](https://docs.openfeature.dev/docs/reference/concepts/provider) for the +following languages: + +- [Go](https://github.com/open-feature/go-sdk-contrib/tree/main/providers/flagsmith) +- [Java](https://github.com/open-feature/java-sdk-contrib/tree/main/providers/flagsmith) +- [.Net](https://github.com/open-feature/dotnet-sdk-contrib/tree/main/src/OpenFeature.Contrib.Providers.Flagsmith) +- [JavaScript/Web](https://github.com/open-feature/js-sdk-contrib/tree/main/libs/providers/flagsmith-client) + +### Beta Providers + +The following OpenFeature Providers are currently being implemented: + +- [Python](https://github.com/Flagsmith/flagsmith-openfeature-provider-python) + +### Planned Providers + +We plan on implementing providers for the following languages as soon as we can: + +- PHP +- Kotlin +- Swift diff --git a/docs/docs/flagsmith-integration/server-side.mdx b/docs/docs/flagsmith-integration/server-side.mdx new file mode 100644 index 000000000000..145f22e04493 --- /dev/null +++ b/docs/docs/flagsmith-integration/server-side.mdx @@ -0,0 +1,1987 @@ +--- +description: Manage your Feature Flags and Remote Config in your Server Side Applications. +sidebar_label: Server Side +sidebar_position: 30 +--- + +import CodeBlock from '@theme/CodeBlock'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import { + JavaVersion, + RustVersion, + DotnetVersion, + ElixirVersion, + NodejsVersion, +} from '@site/src/components/SdkVersions.js'; + +# Server Side SDKs + +:::tip + +Server Side SDKs can run in 2 different modes: Local Evaluation and Remote Evaluation. We recommend +[reading up about the differences](/clients#server-side-sdks) first before integrating the SDKS into your applications. + +::: + +## SDK Overview + + + + +- Version Compatibility: **Python 3.8+** +- Source Code: https://github.com/Flagsmith/flagsmith-python-client + + + + +- Version Compatibility: **JDK 11+** +- Source Code: https://github.com/Flagsmith/flagsmith-java-client + + + + +- Version Compatibility: **.NET core 6.0+** +- Source Code: https://github.com/Flagsmith/flagsmith-dotnet-client + + + + +- Version Compatibility: **Node 18+** +- Source Code: https://github.com/Flagsmith/flagsmith-nodejs-client + + + + +- Version Compatibility: **Ruby 2.4+** +- Source Code: https://github.com/Flagsmith/flagsmith-ruby-client + + + + +- Version Compatibility: **php 7.4+** +- Source Code: https://github.com/Flagsmith/flagsmith-php-client + + + + +- Version Compatibility: **Go 1.18+** +- Source Code: https://github.com/Flagsmith/flagsmith-go-client + + + + +- Version Compatibility: **2021 edition (1.56.0)+** +- Source Code: https://github.com/Flagsmith/flagsmith-rust-client + + + + +- Version Compatibility: **Elixir 1.12+** +- Source Code: https://github.com/Flagsmith/flagsmith-elixir-client + + + + +## Add the Flagsmith package + + + + +```bash +pip install flagsmith +``` + + + + +

Maven

+ + + {` + com.flagsmith + flagsmith-java-client + `} + + {` +`} + + +

Gradle

+ + + {`implementation 'com.flagsmith:flagsmith-java-client:`} + + {`'`} + + +
+ + +

Package Manager console

+ + {`Install-Package Flagsmith -Version `} + + + +

.NET CLI

+ + {`dotnet add package Flagsmith --version `} + + + +

PackageReference

+ + {``} + + +

Paket CLI

+ + {`paket add Flagsmith --version `} + + + +
+ + +```bash +npm install flagsmith-nodejs +``` + + + + +```ruby +gem install flagsmith +``` + + + + +```bash +# Requires PHP 7.4 or newer and ships with GuzzleHTTP. +composer require flagsmith/flagsmith-php-client + +# You can optionally provide your own implementation of PSR-18 and PSR-16. +# You will also need some implementation of PSR-18 and PSR-17, +# for example Guzzle and PSR-16, for example Symfony Cache. +composer require flagsmith/flagsmith-php-client guzzlehttp/guzzle symfony/cache + +# or +composer require flagsmith/flagsmith-php-client symfony/http-client nyholm/psr7 symfony/cache +``` + + + + +```bash +go get github.com/Flagsmith/flagsmith-go-client +``` + + + + + + {`[dependencies] +flagsmith = "~`} + + {`"`} + + + + + + + {`def deps do + [ + {:flagsmith_engine, "~> `} + + {`"}, + ] +end`} + + + +
+ +## Initialise the SDK + +:::tip + +Server-side SDKs must be initialised with Server-side Environment keys. These can be created in the Environment settings +area and should be considered secret. + +::: + + + + +```python +from flagsmith import Flagsmith + +flagsmith = Flagsmith( + environment_key = "FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY" +) +``` + + + + +```java +private static FlagsmithClient flagsmith = FlagsmithClient + .newBuilder() + .setApiKey(System.getenv("FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY")) + .build(); +``` + + + + +```csharp +using Flagsmith; + +var flagsmithClient = new FlagsmithClient( + new FlagsmithConfiguration { + EnvironmentKey = "YOUR_FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY", + } +); +``` + + + + +```javascript +import { Flagsmith } from 'flagsmith-nodejs'; + +const flagsmith = new Flagsmith({ + environmentKey: 'FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY', +}); +``` + + + + +```ruby +require "flagsmith" + +$flagsmith = Flagsmith::Client.new( + environment_key: 'FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY' +) +``` + + + + +```php +use Flagsmith\Flagsmith; + +$flagsmith = new Flagsmith('FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY'); +``` + + + + +```go +import ( + "os" + flagsmith "github.com/Flagsmith/flagsmith-go-client/v3" +) +// Initialise the Flagsmith client +client := flagsmith.NewClient(os.Getenv("FLAGSMITH_ENVIRONMENT_KEY")) +``` + + + + +```rust +use std::env; +use flagsmith::{Flag, Flagsmith, FlagsmithOptions}; + +let options = FlagsmithOptions {..Default::default()}; +let flagsmith = Flagsmith::new( + env::var("FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY") + .expect("FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY not found in environment"), + options, + ); +``` + + + + +```elixir +client_configuration = Flagsmith.Client.new(environment_key: "MY_SDK_KEY") +``` + +Or use global configuration in which case you don't need to create a client or pass configuration options to requests. +All configuration is optional with exception of the `:environment_key`. For instance in `config/config.exs`: + +```elixir +config :flagsmith_engine, :configuration, + environment_key: "", + api_url: "https://edge.api.flagsmith.com/api/v1>", + default_flag_handler: function_defaults_to_not_found, + custom_headers: [{"to add to", "the requests"}], + request_timeout_milliseconds: 5000, + enable_local_evaluation: false, + environment_refresh_interval_milliseconds: 60_000, + retries: 0, + enable_analytics: false +``` + + + + +## Get Flags for an Environment + + + + +```python +# The method below triggers a network request +flags = flagsmith.get_environment_flags() +show_button = flags.is_feature_enabled("secret_button") +button_data = json.loads(flags.get_feature_value("secret_button")) +``` + + + + +```java +Flags flags = flagsmith.getEnvironmentFlags(); +Boolean showButton = flags.isFeatureEnabled(featureName); +Object value = flags.getFeatureValue(featureName); +``` + + + + +```csharp +# Sync +# The method below triggers a network request +var flags = _flagsmithClient.GetEnvironmentFlags().Result; # This method triggers a network request +var showButton = flags.IsFeatureEnabled("secret_button").Result; +var buttonData = flags.GetFeatureValue("secret_button").Result; + +# Async +# The method below triggers a network request +var flags = await _flagsmithClient.GetEnvironmentFlags(); # This method triggers a network request +var showButton = await flags.IsFeatureEnabled("secret_button"); +var buttonData = await flags.GetFeatureValue("secret_button"); +``` + + + + +```javascript +const flags = await flagsmith.getEnvironmentFlags(); +const showButton = flags.isFeatureEnabled('secret_button'); +const buttonData = flags.getFeatureValue('secret_button'); +``` + + + + +```ruby +$flags = $flagsmith.get_environment_flags() +$show_button = $flags.is_feature_enabled('secret_button') +$button_data = $flags.get_feature_value('secret_button') +``` + + + + +```php +$flags = $flagsmith->getEnvironmentFlags(); +$flags->isFeatureEnabled('secret_button'); +$flags->getFeatureValue('secret_button'); +``` + + + + +```go +// The method below triggers a network request +flags, _ := client.GetEnvironmentFlags(ctx) +showButton, _ := flags.IsFeatureEnabled("secret_button") +buttonData, _ := flags.GetFeatureValue("secret_button") +``` + + + + +```rust +// The method below triggers a network request +let flags = flagsmith.get_environment_flags().unwrap(); + +let show_button = flags.is_feature_enabled("secret_button").unwrap(); + +let button_data = flags.get_feature_value_as_string("secret_button").unwrap(); +``` + + + + +```elixir +# The method below triggers a network request +{:ok, %Flagsmith.Schemas.Flags{} = flags} = Flagsmith.Client.get_environment_flags(client_configuration) + +secret_button_enabled? = Flagsmith.Client.is_feature_enabled(flags, "secret_button") +secret_button_feature_value = Flagsmith.Client.get_feature_value(flags, "secret_button") +``` + + + + +## Get Flags for an Identity + + + + +```python +identifier = "delboy@trotterstraders.co.uk" +traits = {"car_type": "robin_reliant"} + +# The method below triggers a network request +identity_flags = flagsmith.get_identity_flags(identifier=identifier, traits=traits) +show_button = identity_flags.is_feature_enabled("secret_button") +button_data = json.loads(identity_flags.get_feature_value("secret_button")) +``` + + + + +```java +String identifier = "delboy@trotterstraders.co.uk" +Map traits = new HashMap(); +traits.put("car_type", "robin_reliant"); + +// The method below triggers a network request +Flags flags = flagsmith.getIdentityFlags(identifier, traits); +Boolean showButton = flags.isFeatureEnabled(featureName); +Object value = flags.getFeatureValue(featureName); +``` + + + + +```csharp +var identifier = "delboy@trotterstraders.co.uk"; +var traitKey = "car_type"; +var traitValue = "robin_reliant"; +var traitList = new List { new Trait(traitKey, traitValue) }; + +# Sync +var flags = _flagsmithClient.GetIdentityFlags(identifier, traitList).Result; +var showButton = flags.IsFeatureEnabled("secret_button").Result; + +# Async +var flags = await _flagsmithClient.GetIdentityFlags(identifier, traitList); +var showButton = await flags.IsFeatureEnabled("secret_button"); +``` + + + + +```javascript +const identifier = 'delboy@trotterstraders.co.uk'; +const traitList = { car_type: 'robin_reliant' }; + +const flags = await flagsmith.getIdentityFlags(identifier, traitList); +var showButton = flags.isFeatureEnabled('secret_button'); +var buttonData = flags.getFeatureValue('secret_button'); +``` + + + + +```ruby +$identifier = 'delboy@trotterstraders.co.uk' +$traits = {'car_type': 'robin_reliant'} + +$flags = $flagsmith.get_identity_flags($identifier, **$traits) +$show_button = $flags.is_feature_enabled('secret_button') +$button_data = $flags.get_feature_value('secret_button') +``` + + + + +```php +$identifier = 'delboy@trotterstraders.co.uk'; +$traits = (object) [ 'car_type' => 'robin_reliant' ]; + +$flags = $flagsmith->getIdentityFlags($identifier, $traits); +$showButton = $flags->isFeatureEnabled('secret_button'); +$buttonData = $flags->getFeatureValue('secret_button'); +``` + + + + +```go +trait := flagsmith.Trait{TraitKey: "trait", TraitValue: "trait_value"} +traits = []*flagsmith.Trait{&trait} + +// The method below triggers a network request +flags, _ := client.GetIdentityFlags(ctx, identifier, traits) + +showButton, _ := flags.IsFeatureEnabled("secret_button") +buttonData, _ := flags.GetFeatureValue("secret_button") +``` + + + + +```rust +use flagsmith::models::SDKTrait; +use flagsmith_flag_engine::types::{FlagsmithValue, FlagsmithValueType}; + +let identifier = "delboy@trotterstraders.co.uk"; + +let traits = vec![SDKTrait::new( + "car_type".to_string(), + FlagsmithValue { + value: "robin_reliant".to_string(), + value_type: FlagsmithValueType::String, + }, + )]; + +// The method below triggers a network request +let identity_flags = flagsmith.get_identity_flags(identifier, Some(traits), None).unwrap(); + +let show_button = identity_flags.is_feature_enabled("secret_button").unwrap(); +let button_data = identity_flags.get_feature_value_as_string("secret_button").unwrap(); +``` + + + + +```elixir +# The method below triggers a network request +{:ok, flags} = Flagsmith.Client.get_identity_flags( + client_configuration, + "user-a", + [%{trait_key: "is_subscribed", trait_value: false}] +) + +secret_button_enabled? = Flagsmith.Client.is_feature_enabled(flags, "secret_button") +secret_button_feature_value = Flagsmith.Client.get_feature_value(flags, "secret_butteon") +``` + + + + +### When running in [Remote Evaluation mode](/clients#remote-evaluation) + +- When requesting flags for an identity, all the traits defined in the SDK will automatically be persisted against the + identity within the Flagsmith API. +- Traits passed to the SDK will be added to all the other previously persisted traits associated with that identity. +- This full set of traits are then used to evaluate the flag values for the identity. +- This all happens in a single request/response. + +### When running in [Local Evaluation mode](/clients#local-evaluation) + +- _Only_ the traits provided to the SDK at runtime will be used. Local Evaluation mode, by design, does not make any + network requests to the Flagsmith API when evaluating flags for an identity. + - When running in Local Evaluation Mode, the SDK requests the + [Environment Document](/clients#the-environment-document) from the Flagsmith API. This contains all the + information required to make flag evaluations, but it does _not_ contain any trait data. + +## Managing Default Flags + +Default flags are configured by passing in a function that is called when a flag cannot be found or if the network +request to the API fails when retrieving flags. + + + + +```python +from flagsmith import Flagsmith +from flagsmith.models import DefaultFlag + +def default_flag_handler(feature_name: str) -> DefaultFlag: + """ + Function that will be used if the API doesn't respond, or an unknown + feature is requested + """ + if feature_name == "secret_button": + return DefaultFlag( + enabled=False, + value=json.dumps({"colour": "#b8b8b8"}), + feature_name="secret_button", + ) + ], + return DefaultFlag(False, None) + +flagsmith = Flagsmith( + environment_key="FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY", + default_flag_handler=default_flag_handler, +) +``` + + + + +```java +private static FlagsmithClient flagsmith = FlagsmithClient + .newBuilder() + .setDefaultFlagValueFunction(HelloController::defaultFlagHandler) + .setApiKey(System.getenv("FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY")) + .build(); + +private static DefaultFlag defaultFlagHandler(String featureName) { + DefaultFlag flag = new DefaultFlag(); + flag.setEnabled(Boolean.FALSE); + + if (featureName.equals("secret_button")) { + flag.setValue("{\"colour\": \"#ababab\"}"); + } else { + flag.setValue(null); + } + + return flag; +} +``` + + + + +```csharp +using Flagsmith; + +var config = new FlagsmithConfiguration +{ + EnvironmentKey = "YOUR_SERVER_SIDE_ENVIRONMENT_KEY", + DefaultFlagHandler = defaultFlagHandler +} +var flagsmithClient = new FlagsmithClient(config); + +static Flag defaultFlagHandler(string featureName) +{ + if (featureName == "secret_button") + return new Flag(new Feature("secret_button"), enabled: false, value: JsonConvert.SerializeObject(new { colour = "#b8b8b8" }).ToString()); + else return new Flag() { }; +} +``` + + + + +```javascript +const flagsmith = new Flagsmith({ + environmentKey, + enableLocalEvaluation: true, + defaultFlagHandler: (str) => { + return { enabled: false, isDefault: true, value: { colour: '#ababab' } }; + }, +}); +``` + + + + +```ruby +$flagsmith = Flagsmith::Client.new( + environment_key: ', + default_flag_handler: lambda { |feature_name| + Flagsmith::Flags::DefaultFlag.new( + enabled: false, value: {'colour': '#ababab'}.to_json + ) + } +) +``` + + + + +```php +$flagsmith = (new Flagsmith('FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY')) + ->withDefaultFlagHandler(function ($featureName) { + $defaultFlag = (new DefaultFlag()) + ->withEnabled(false)->withValue(null); + if ($featureName === 'secret_button') { + return $defaultFlag->withValue('{"colour": "#ababab"}'); + } + + return $defaultFlag; + }); +``` + + + + +```go +func DefaultFlagHandler(featureName string) (flagsmith.Flag, error) { + return flagsmith.Flag{ + FeatureName: featureName, + IsDefault: true, + Value: `{"colour": "#FFFF00"}`, + Enabled: true, + }, nil +} + +client := flagsmith.NewClient(os.Getenv("FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY"), + flagsmith.WithDefaultHandler(DefaultFlagHandler), +) + +``` + + + + +```rust + +use flagsmith::{Flag, Flagsmith, FlagsmithOptions}; + +fn default_flag_handler(feature_name: &str) -> Flag { + let mut flag: Flag = Default::default(); + if feature_name == "secret_button" { + flag.value.value_type = FlagsmithValueType::String; + flag.value.value = serde_json::json!({"colour": "#b8b8b8"}).to_string(); + } + return flag; +} + +let options = FlagsmithOptions { + default_flag_handler: Some(default_flag_handler), + ..Default::default() +}; + +let flagsmith = Flagsmith::new( + env::var("FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY") + .expect("FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY not found in environment"), + options, + ); + +``` + + + + +```elixir +flag_handler = + fn name -> + case name == "special_feature" do + true -> + %Flagsmith.Schemas.Flag{feature_name: name, value: "special", enabled: true} + _ -> :not_found + end + end + +client_configuration = Flagsmith.Client.new(environment_key: "FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY", default_flag_handler: flag_handler) +``` + + + + +### Using an Offline Handler + +:::info + +Offline handlers are still in active development. We are building them for all our SDKs; those that are production ready +are listed below. + +Progress on the remaining SDKs can be seen [here](https://github.com/Flagsmith/flagsmith/issues/2024). + +::: + +Flagsmith SDKs can be configured to include an offline handler which has 2 functions: + +1. It can be used alongside [Offline Mode](server-side#offline-mode) to evaluate flags in environments with no network + access +2. It can be used as a means of defining the behaviour for evaluating default flags, when something goes wrong with the + regular evaluation process. To do this, simply set the offline handler initialisation parameter without enabling + offline mode. + +To use it as a default handler, we recommend using the [flagsmith CLI](https://github.com/Flagsmith/flagsmith-cli) to +generate the [Environment Document](/clients#the-environment-document) and use our LocalFileHandler class, but you can +also create your own offline handlers, by extending the base class. + + + + +```python +# Using the built-in local file handler + +local_file_handler = LocalFileHandler(environment_document_path="/app/environment.json") +flagsmith = Flagsmith(..., offline_handler=local_file_handler) + +# Defining a custom offline handler + +class MyCustomOfflineHandler(BaseOfflineHandler): + def get_environment(self) -> EnvironmentModel: + return some_function_to_get_the_environment() +``` + + + + + +```java +// Using the built-in local file handler + +FlagsmithConfig flagsmithConfig = FlagsmithConfig.newBuilder() + .withOfflineHandler(new LocalFileHandler("/app/environment.json")) + ... + .build() + +// Defining a custom offline handler + +public class MyCustomOfflineHandler implements IOfflineHandler: + public EnvironmentModel getEnvironment() { + return someMethodToGetTheEnvironment() + } +``` + + + + +```csharp +// Using the built-in local file handler +var localFileHandler = new LocalFileHandler("path_to_environment_file/environment_file.json"); +var flagsmithClient = new FlagsmithClient( + new FlagsmithConfiguration { + OfflineMode = true, + OfflineHandler = localFileHandler + } +); + +// Defining a custom offline handler +public class MyCustomOfflineHandler: BaseOfflineHandler +{ + public override EnvironmentModel GetEnvironment() + { + return someMethodToGetTheEnvironment(); + } +} +``` + + + + +Use LocalFileHandler +to read an environment file generated by the [Flagsmith CLI](/clients/CLI): + +```typescript +import { Flagsmith, LocalFileHandler } from 'flagsmith-nodejs'; + +const flagsmith = new Flagsmith({ + offlineMode: true, + offlineHandler: new LocalFileHandler('./flagsmith.json'), +}); +``` + +To create your own offline handler, implement the `BaseOfflineHandler` interface. It must return an +{/* prettier-ignore */}EnvironmentModel +object: + +```typescript +import type { BaseOfflineHandler, EnvironmentModel } from 'flagsmith-nodejs'; + +class CustomOfflineHandler implements BaseOfflineHandler { + getEnvironment(): EnvironmentModel { + // ... + } +} +``` + + + + +```ruby +# Using the built-in local file handler + +offline_handler = \ +Flagsmith::OfflineHandlers::LocalFileHandler.new("environment.json") + +# Instantiate the client with offline mode set to true + +flagsmith = Flagsmith::Client.new( + offline_mode: true, + offline_handler: offline_handler, +) + +# Defining a custom offline handler + +class MyCustomOfflineHandler + def environment + # Some code providing the environment for the handler + end +end +``` + + + + +```rust +# Using the built-in local file handler + +let handler = offline_handler::LocalFileHandler::new("environment.json").unwrap(); + +# Instantiate the client with offline handler + + let flagsmith_options = FlagsmithOptions { + offline_handler: Some(Box::new(handler)), + ..Default::default() +}; + +let flagsmith = Flagsmith::new(ENVIRONMENT_KEY.to_string(), flagsmith_options); + + +# Defining a custom offline handler +impl OfflineHandler for MyCustomOfflineHandler { + fn get_environment(&self) -> Environment { + ... + } +} + +``` + + + + +```go +# Using the built-in local file handler + +envJsonPath := "./fixtures/environment.json" +offlineHandler, err := flagsmith.NewLocalFileHandler(envJsonPath) + +# Instantiate the client with offline handler + +flagsmith := flagsmith.NewClient(EnvironmentAPIKey, flagsmith.WithOfflineHandler(offlineHandler), + flagsmith.WithBaseURL(server.URL+"/api/v1/")) + + +# Defining a custom offline handler +type CustomOfflineHandler struct { + ... +} + +func (handler *CustomOfflineHandler) GetEnvironment() *environments.EnvironmentModel { + ... +} + +``` + + + + + +```php +// Using the built-in local file handler + +$offline_handler = new LocalFileHandler("/path/to/environment.json") + +// Instantiate the client with offline mode set to true + +$flagsmith = new Flagsmith( + offline_mode: true, + offline_handler: offline_handler, +) + +// Defining a custom offline handler + +class LocalFileHandler implements IOfflineHandler +{ + public function getEnvironment() + { + // Some code providing the environment for the handler + } +} +``` + + + + + +## Network Behaviour + +The Server Side SDKS share the same network behaviour across the different languages: + +### Remote Evaluation Mode Network Behaviour + +- A blocking network request is made every time you make a call to get an environment flags. In Python, for example, + `flagsmith.get_environment_flags()` will trigger this request. +- A blocking network request is made every time you make a call to get an identities flags. In Python, for example, + `flagsmith.get_identity_flags(identifier=identifier, traits=traits)` will trigger this request. + +### Local Evaluation Mode Network Behaviour + +:::info + +When using Local Evaluation, it's important to read up on the [Pros, Cons and Caveats](/clients/#pros-cons-and-caveats). + +To use Local Evaluation mode, you must use a Server Side key. + +::: + +- When the SDK is initialised, it will make an asynchronous network request to retrieve details about the environment. +- Every 60 seconds (by default), it will repeat this aysnchronous request to ensure that the environment information it + has is up to date. + +To achieve Local Evaluation, in most languages, the SDK spawns a separate thread (or equivalent) to poll the API for +changes to the environment. In certain languages, you may be required to terminate this thread before cleaning up the +instance of the Flagsmith client. Languages in which this is necessary are provided below. + + + + +```java +// available from v5.0.5 +flagsmith.close(); +``` + + + + +```javascript +flagsmith.close(); +``` + + + + +Since PHP does not share state between requests, you **have** to implement caching to get the benefits of Local +Evaluation mode. Please see [caching](#caching) below. + + + + +### Offline Mode + +To run the SDK in a fully offline mode, you can set the client to offline mode. This will prevent the SDK from making +any calls to the Flagsmith API. To use offline mode, you must also provide an +[offline handler](server-side#using-an-offline-handler). See [Configuring the SDK](server-side#configuring-the-sdk) for +more details on initialising the SDK in offline mode. + +## Configuring the SDK + +You can modify the behaviour of the SDK during initialisation. Full configuration options are shown below. + + + + +```python +flagsmith = Flagsmith( + # Your API Token. + # Note that this is either the `Environment API` key or the `Server Side SDK Token` + # depending on if you are using Local or Remote Evaluation + # Required. + environment_key = "FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY", + + # Controls which mode to run in; local or remote evaluation. + # See the `SDKs Overview Page` for more info + # Optional. + # Defaults to False. + enable_local_evaluation = False, + + # Override the default Flagsmith API URL if you are self-hosting. + # Optional. + # Defaults to https://edge.api.flagsmith.com/api/v1/ + api_url = "https://api.yourselfhostedflagsmith.com/api/v1/", + + # The network timeout in seconds. + # Optional. + # Defaults to 10 seconds + request_timeout_seconds = 10, + + # When running in local evaluation mode, defines + # how often to request an updated Environment document in seconds + # Optional + # Defaults to 60 seconds + environment_refresh_interval_seconds: int = 60, + + # A `urllib3` Retries object to control network retry policy + # See https://urllib3.readthedocs.io/en/latest/reference/urllib3.util.html#urllib3.util.Retry + # Optional + # Defaults to None + retries: Retry = None, + + # Controls whether Flag Analytics data is sent to the Flagsmith API + # See https://docs.flagsmith.com/advanced-use/flag-analytics + # Optional + # Defaults to False + enable_analytics: bool = False, + + # You can pass custom headers to the Flagsmith API with this Dictionary. + # This can be helpful, for example, when sending request IDs to help trace requests. + # Optional + # Defaults to None + custom_headers: typing.Dict[str, typing.Any] = None, + + # You can specify a function to handle returning defaults in the case that + # the request to flagsmith fails or the flag requested is not included in the + # response + # Optional + default_flag_handler = lambda feature_name: return DefaultFlag(enabled=False, value=None), + + # (Available in 3.2.0+) Pass a mapping of protocol to proxy URL as per + # https://requests.readthedocs.io/en/latest/api/#requests.Session.proxies + # Optional + proxies: typing.Dict[str, str] = None, + + # (Available in 3.4.0+) Set the SDK into offline mode. + # Optional + # Defaults to False + offline_mode: bool = False, + + # (Available in 3.4.0+) Provide an offline handler to use with offline mode, or + # as a means of returning default flags. + # Optional + # Defaults to None + offline_handler: BaseOfflineHander = None, +) +``` + + + + +```java +// The configuration for the Java client is currently split across the FlagsmithClient and +// FlagsmithConfig class, we are working to improve that in a future release. + +private static FlagsmithClient flagsmith = FlagsmithClient + .newBuilder() + // Your API Token. + // Note that this is either the `Environment API` key or the `Server Side SDK Token` + // depending on if you are using Local or Remote Evaluation + // Required. + .setApiKey(System.getenv("FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY")) + + // You can specify default Flag values on initialisation. + // Optional + .setDefaultFlagValueFunction(HelloController::defaultFlagHandler) + + // Controls which mode to run in; local or remote evaluation. + // See the `SDKs Overview Page` for more info + // Optional. + // Defaults to False. + .withLocalEvaluation(True) + + // Add custom headers which will be sent with each network request + // to the Flagsmith API. + // Optional. + // Defaults to no custom headers. + .withCustomHttpHeaders(new HashMap() {{ + put("header", "value"); + }}) + + // Enable in-memory caching for the Flagsmith API. + // Optional. + // Defaults to not cache anything. + .withCache(FlagsmithCacheConfig.builder().enableEnvLevelCaching("cache-key").build()) + + .withConfiguration(FlagsmithConfig.builder() + // Override the default Flagsmith API URL if you are self-hosting. + // Optional. + // Defaults to https://edge.api.flagsmith.com/api/v1/ + .baseUri("https://api.yourselfhostedflagsmith.com/api/v1/") + + // The network timeout in milliseconds. + // See https://square.github.io/okhttp/4.x/okhttp/okhttp3/ for details + // Defaults are: + // connect: 2000 + // write: 5000 + // read: 5000 + // Optional. + .connectTimeout() + .writeTimeout() + .readTimeout() + + // Override the sslSocketFactory + // See https://square.github.io/okhttp/4.x/okhttp/okhttp3/ for details + // Optional. + .sslSocketFactory(SSLSocketFactory sslSocketFactory, X509TrustManager trustManager) + + // Add a custom HTTP interceptor in the form of an okhttp3.Interceptor + // object + // Optional + .addHttpInterceptor(interceptor) + + // Add a custom java.net.Proxy to the OkHttp client + // Optional + .withProxy(proxy) + + // Add a custom com.flagsmith.config.Retry object to configure the + // backoff / retry configuration + // Optional + // Defaults to Retry(3) + .retries(retries) + + // Enable local evaluation mode + // () + // Optional + // Defaults to false + .withLocalEvaluation(true) + + // Set environment refresh rate with polling manager. + // Only needed when local evaluation is true. + // Optional. + // Defaults to 60 seconds + .withEnvironmentRefreshIntervalSeconds(Integer seconds) + + // Controls whether Flag Analytics data is sent to the Flagsmith API + // See https://docs.flagsmith.com/advanced-use/flag-analytics + // Optional + // Defaults to False + .withEnableAnalytics(Boolean enable) + + // (Available in v7.2.0+) Set the SDK into offline mode. + // Optional + // Defaults to False + .withOfflineMode(Boolean enable) + + // (Available in v7.2.0+) Provide an offline handler to use with offline mode, or as a means of returning default flags. + // Optional + .withOfflineHandler(IOfflineHandler offlineHandler) + + .build()) + + .build(); +``` + + + + +```csharp +var flagsmithClient = new FlagsmithClient( + new FlagsmithConfiguration { + # Your environment's SDK key. This should be a client-side key if you are using remote evaluation or a + # server-side key if you are using local evaluation. + # Required. + EnvironmentKey = "FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY", + + # An optional flag handler used as a fallback if the client is unable to evaluate flags for any reason. + DefaultFlagHandler = defaultFlagHandler, + + # If you are not using Flagsmith SaaS, set this to your Flagsmith API URL. + # Defaults to https://edge.api.flagsmith.com/api/v1/ + ApiUri: new Uri("https://flagsmith.example.com/api/v1/"), + + # Controls which mode to run in; local or remote evaluation. Defaults to false (remote evaluation). + # See the `SDKs Overview Page` for more info. + EnableLocalEvaluation = false, + + # Controls whether flag analytics data is sent to the Flagsmith API. Defaults to false. + # See https://docs.flagsmith.com/advanced-use/flag-analytics + EnableAnalytics = false, + + # When running in local evaluation mode, defines how often to update the environment document. + # Defaults to 60 seconds. + EnvironmentRefreshInterval = TimeSpan.FromSeconds(60), + + # All HTTP requests made by this client will include these additional headers. + # This can be helpful, for example, if you are self-hosting Flagsmith and want to add trace IDs to all requests. + CustomHeaders = new Dictionary(), + + # How many times to retry failed HTTP requests. Defaults to 1. + Retries = 1, + + # The network timeout in seconds. If not specified, the HTTP client's default timeout is used. + RequestTimeout = 10, + } +); +``` + + + + +```ruby +$flagsmith = Flagsmith::Client.new( + # Your API Token. + # Note that this is either the `Environment API` key or the `Server Side SDK Token` + # depending on if you are using Local or Remote Evaluation + # Required. + environment_key = "FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY", + + # Controls which mode to run in; local or remote evaluation. + # See the `SDKs Overview Page` for more info + # Optional. + # Defaults to false. + enable_local_evaluation = false, + + # Override the default Flagsmith API URL if you are self-hosting. + # Optional. + # Defaults to https://edge.api.flagsmith.com/api/v1/ + api_url = "https://api.yourselfhostedflagsmith.com/api/v1/", + + # The network timeout in seconds. + # Optional. + # Defaults to 10 seconds + request_timeout_seconds = 10, + + # When running in local evaluation mode, defines + # how often to request an updated Environment document in seconds + # Optional + # Defaults to 60 seconds + environment_refresh_interval_seconds = 60, + + # A faraday retry object to control network retry policy + # See https://www.rubydoc.info/gems/faraday/0.15.3/Faraday/Request/Retry + # Optional + # Defaults to nil + retries = nil, + + # Controls whether Flag Analytics data is sent to the Flagsmith API + # See https://docs.flagsmith.com/advanced-use/flag-analytics + # Optional + # Defaults to False + enable_analytics = false, + + # You can pass custom headers to the Flagsmith API with this Dictionary. + # This can be helpful, for example, when sending request IDs to help trace requests. + # Optional + # Defaults to nill + custom_headers = nil, + + # You can specify a function to handle returning defaults in the case that + # the request to flagsmith fails or the flag requested is not included in the + # response + # Optional + default_flag_handler = lambda { |feature_name| Flagsmith::DefaultFlag.new(enabled=false, value=nil) } +) +``` + + + + +```typescript +import { Flagsmith } from 'flagsmith-nodejs'; +import type { EnvironmentModel } from 'flagsmith-nodejs'; + +const flagsmith = new Flagsmith({ + /* + Your API Token. + Note that this is either the `Environment API` key or the `Server Side SDK Token` + depending on if you are using Local or Remote Evaluation + Required. + */ + environmentKey: 'FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY', + + /* + Override the default Flagsmith API URL if you are self-hosting. + Optional. + Defaults to https://edge.api.flagsmith.com/api/v1/ + */ + apiUrl: 'https://api.yourselfhostedflagsmith.com/api/v1/', + + /* + Adds caching support + Optional + See https://docs.flagsmith.com/clients/server-side#caching + */ + cache: { + get: (key: string) => Promise.resolve(), + set: (k: string, v: Flags) => Promise.resolve(), + }, + + /* + Custom http headers can be added to the http client + Optional + */ + customHeaders: { aHeader: 'aValue' }, + + /* + Controls whether Flag Analytics data is sent to the Flagsmith API + See https://docs.flagsmith.com/advanced-use/flag-analytics + Optional + Defaults to false + */ + enableAnalytics: true, + + /* + Controls which mode to run in; local or remote evaluation. + See the `SDKs Overview Page` for more info + Optional. + Defaults to false. + */ + enableLocalEvaluation: true, + + /* + Set environment refresh rate with polling manager. + Only needed when local evaluation is true. + Optional. + Defaults to 60 seconds + */ + environmentRefreshIntervalSeconds: 60, + + /* + The network timeout in seconds. + Optional. + Defaults to 10 seconds + */ + requestTimeoutSeconds: 30, + + /* + You can specify default Flag values on initialisation. + Optional + */ + defaultFlagHandler: (featureName: string) => { + return { enabled: false, isDefault: true, value: null }; + }, + + /* + A callback for whenever the environment model is updated or there is an error retrieving it. + This is only used in local evaluation mode. + Optional + */ + onEnvironmentChange: (error: Error | null, result: EnvironmentModel) => {}, +}); +``` + + + + +```php +$flagsmith = new Flagsmith( + /* + Your API Token. + Note that this is either the `Environment API` key or the `Server Side SDK Token` + depending on if you are using Local or Remote Evaluation + Required. + */ + string $apiKey, + + /* + Override the default Flagsmith API URL if you are self-hosting. + Optional. + Defaults to https://edge.api.flagsmith.com/api/v1/ + */ + string $host = self::DEFAULT_API_URL, + + /* + Custom http headers can be added to the http client + Optional + */ + object $customHeaders = null, + + /* + Set environment refresh rate with polling manager. + This also enables local evaluation. + Optional. + Defaults to null + */ + int $environmentTtl = null, + + /* + Retry Object, instance of Flagsmith\Utils\Retry + Retry configuration for api calls. + Defaults to 3 retries for every api call. + */ + Retry $retries = null, + + /* + Controls whether Flag Analytics data is sent to the Flagsmith API + See https://docs.flagsmith.com/advanced-use/flag-analytics + Optional + Defaults to false + */ + bool $enableAnalytics = false, + + /* + You can specify default Flag values on initialisation. + Optional + */ + Closure $defaultFlagHandler = null + + /* + (Available in 4.4.0+) Set the SDK into offline mode + Optional + */ + bool $offlineMode = false, + + # (Available in 4.4.0+) Provide an offline handler to use with offline mode, or + # as a means of returning default flags. + # Optional + IOfflineHandler $offlineHandler = null, +); +``` + + + + +```go +client := flagsmith.NewClient(os.Getenv("FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY"), + // Override the default Flagsmith API URL if you are self-hosting. + // Defaults to https://edge.api.flagsmith.com/api/v1/ + flagsmith.WithBaseURL("http://localhost:8080/api/v1/"), + + // Controls which mode to run in; local or remote evaluation. + // See the `SDKs Overview Page` for more info + // Defaults to False + func WithLocalEvaluation(ctx context.Context), + + // The network timeout in seconds. + flagsmith.WithRequestTimeout(10*time.Second), + + // When running in local evaluation mode, defines + // how often to request an updated Environment document + // Defaults to 60 seconds + flagsmith.WithEnvironmentRefreshInterval(60*time.Second), + + // Controls whether Flag Analytics data is sent to the Flagsmith API + // See https://docs.flagsmith.com/advanced-use/flag-analytics + flagsmith.WithAnalytics(ctx), + + // Sets `resty.Client` options. `SetRetryCount` and `SetRetryWaitTime` + // Ref: https://pkg.go.dev/github.com/go-resty/resty/v2#Client.SetRetryCount + // https://pkg.go.dev/github.com/go-resty/resty/v2#Client.SetRetryWaitTime + flagsmith.WithRetries(3, 5*time.Second), + + // You can pass custom headers to the Flagsmith API with this Dictionary. + // This can be helpful, for example, when sending request IDs to help trace requests. + flagsmith.WithCustomHeaders(map[string]string{ + "Content-Type": "application/json", + "Accept": "application/json", + }), + + // You can specify a function to handle returning defaults in the case that + // the request to flagsmith fails or the flag requested is not included in the + // response + flagsmith.WithDefaultHandler(defaultFlagHandler), + + // WithOfflineMode returns an Option function that enables the offline mode. + flagsmith.WithOfflineHandler(offlineHandler) + + // WithOfflineMode returns an Option function that enables the offline mode. + // (before using this option, you should set the offline handler) + flagsmith.WithOfflineMode() + + // Allows the client to use any logger that implements the `Logger` interface. + flagsmith.WithLogger(ctx), + + // WithProxy returns an Option function that sets the proxy(to be used by internal resty client). + // The proxyURL argument is a string representing the URL of the proxy server to use, e.g. "http://proxy.example.com:8080". + func WithProxy(proxyURL string) Option { + return func(c *Client) { + c.client.SetProxy(proxyURL) + } + } + + // WithRestyClient allows you to provide a custom resty client for making HTTP requests. + // This gives you more control over the HTTP client configuration. + // Only one of resty or HTTP custom client can be provided + // Can not be used simultaneously with Client related options (WithRequestTimeout, WithRetries, WithCustomHeaders, WithProxy) + flagsmith.WithRestyClient(restyClient) + + // WithHTTPClient allows you to provide a custom http client for making HTTP requests. + // This is useful when you need to customize the underlying HTTP client behavior. + // Only one of resty or HTTP custom client can be provided + // Can not be used simultaneously with Client related options (WithRequestTimeout, WithRetries, WithCustomHeaders, WithProxy) + flagsmith.WithHTTPClient(httpClient) +) +``` + + + + +```rust +use reqwest::header::{self, HeaderMap}; +// Optional Arguments +let options = FlagsmithOptions { + // Override the default Flagsmith API URL if you are self-hosting. + // Defaults to https://edge.api.flagsmith.com/api/v1/ + api_url: "https://edge.flagsmith.com/api/v1/".to_string(), + + // You can pass custom headers to the Flagsmith API with this HashMap + // This can be helpful, for example, when sending request IDs to help trace requests. + // Defaults to an empty header::HeaderMap. + custom_headers: header::HeaderMap::new(), + + // The network timeout in seconds. + // Defaults to 10 seconds + request_timeout_seconds: 10, + + // Controls which mode to run in; local or remote evaluation. + // See the `SDKs Overview Page` for more info + // Defaults to False. + enable_local_evaluation: false, + + // When running in local evaluation mode, defines + // how often to request an updated Environment document in milliseconds. + // Defaults to 60 seconds + environment_refresh_interval_mills: 60* 1000, + + // Controls whether Flag Analytics data is sent to the Flagsmith API + // See https://docs.flagsmith.com/advanced-use/flag-analytics + // Defaults to False + enable_analytics: false, + + //Function that will be used if the API doesn't respond, or an unknown + // feature is Requested + // Defaults to None + default_flag_handler: None + + // Provide an offline handler to use with offline mode, or as a means of returning default flags + offline_handler: None + + // Set the SDK into offline mode(offline_handler must be set) + offline_mode: false +}; + +// Required Arguments +// Your API Token. +// Note that this is either the `Environment API` key or the `Server Side SDK Token` +// depending on if you are using Local or Remote Evaluation +let FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY = "some_key".to_string(); + +let flagsmith = Flagsmith::new( + FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY, + options, + ); + + +``` + + + + +Application level Configuration + +```elixir +# The only required option is the `:environment_key` + +config :flagsmith_engine, :configuration, + # + # Your API Token. + # Note that this is either the `Environment API` key or the + # `Server Side SDK Token` depending on if you are using Local or + # Remote Evaluation + environment_key: "FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY", + # + # Override the default Flagsmith API URL if you are self-hosting. + # Defaults to https://edge.api.flagsmith.com/api/v1/ + api_url: "https://api.yourselfhostedflagsmith.com/api/v1", + # + # You can specify a function to handle returning defaults in the case that + # the request to flagsmith fails or the flag requested is not included in the + # response, defaults to returning :not_found` + default_flag_handler: function_defaults_to_not_found, + # + # You can pass custom headers to the Flagsmith API as a list of `header` `value` + # tuples, for example, when sending request IDs to help trace requests, defaults + # to an empty list. + custom_headers: [{"to add to", "the requests"}], + # + # Network timeout in milliseconds, defaults to 5_000 + request_timeout_milliseconds: 5000, + # + # Controls which mode to run in; local or remote evaluation. + # See the `SDKs Overview Page` for more info, defaults to false + enable_local_evaluation: false, + # + # When running in local evaluation mode, defines how often to request + # an updated Environment document in milliseconds, defaults to 1 minute + environment_refresh_interval_milliseconds: 60_000, + # + # Defines how many retries the HTTP adapter is allowed to execute before + # deeming the request failed, defaults to 0 + retries: 0, + # + # Controls whether Flag Analytics data is sent to the Flagsmith API + # See https://docs.flagsmith.com/advanced-use/flag-analytics, defaults to false + enable_analytics: false + +``` + +Or when starting a client or making a request, allows the exact same options as when configuring through the application +configuration. + +```elixir +client_configuration = Flagsmith.Client.new( + environment_key: "FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY", + api_url: "https://api.yourselfhostedflagsmith.com/api/v1", + default_flag_handler: function_defaults_to_not_found, + custom_headers: [{"to add to", "the requests"}], + request_timeout_milliseconds: 5000, + enable_local_evaluation: false, + environment_refresh_interval_milliseconds: 60_000, + retries: 0, + enable_analytics: false +) + +{:ok, flags} = Flagsmith.Client.get_environment_flags(client_configuration) + +# or + +{:ok, flags} = Flagsmith.Client.get_environment_flags( + environment_key: "FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY", + api_url: "https://api.yourselfhostedflagsmith.com/api/v1", + default_flag_handler: function_defaults_to_not_found, + custom_headers: [{"to add to", "the requests"}], + request_timeout_milliseconds: 5000, + enable_local_evaluation: false, + environment_refresh_interval_milliseconds: 60_000, + retries: 0, + enable_analytics: false +) +``` + + + + +## Caching + +Some SDKs support caching flags retrieved from the Flagsmith API, or calculated from your environment definition if +using Local Evaluation. + + + + +If you would like to use in-memory caching, you will need to enable it (it is disabled by default). The main advantage +of using in-memory caching is that you can reduce the number of HTTP calls performed to fetch flags. + +Flagsmith uses [Caffeine](https://github.com/ben-manes/caffeine), a high performance, near optimal caching library. + +If you enable caching on the Flagsmith client without setting any values (as shown below), the following default values +will be set for you: + +- `maxSize(10)` +- `expireAfterWrite(5, TimeUnit.MINUTES)` +- project level caching will be disabled by default (i.e. only enabled if you configure a caching key) + +```java +// use in-memory caching with Flagsmith defaults as described above +final FlagsmithClient flagsmithClient = FlagsmithClient.newBuilder() + .setApiKey("FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY") + .withConfiguration(FlagsmithConfig + .newBuilder() + .baseURI("https://flagsmith.example.com/api/v1/") + .build()) + .withCache(FlagsmithCacheConfig + .newBuilder() + .build()) + .build(); +``` + +If you would like to change the default settings, you can overwrite them by using the available builder methods: + +```java +// use in-memory caching with custom configuration +final FlagsmithClient flagsmithClient = FlagsmithClient.newBuilder() + .setApiKey("FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY") + .withConfiguration(FlagsmithConfig + .newBuilder() + .baseURI("https://flagsmith.example.com/api/v1/") + .build()) + .withCache(FlagsmithCacheConfig + .newBuilder() + .maxSize(100) + .expireAfterWrite(10, TimeUnit.MINUTES) + .recordStats() + .enableEnvLevelCaching("some-key-to-avoid-clashing-with-user-identifiers") + .build()) + .build(); +``` + +The user identifier is used as the cache key, this provides granular control over the cache should you require it. If +you would like to manipulate the cache: + +```java +// this will return null if caching is disabled +final FlagsmithCache cache = flagsmithClient.getCache(); +// you can now discard a single or all entries in the cache +cache.invalidate("user-identifier"); +// or +cache.invalidateAll(); +// get stats (if you have enabled them in the cache configuration, otherwise all values will be zero) +final CacheStats stats = cache.stats(); +// check if flags for a user identifier are cached +final FlagsAndTraits flags = cache.getIfPresent("user-identifier"); +``` + +Since the user identifier is used as the cache key, you need to configure a cache key to enable project level caching. +Make sure you select a project level cache key that will never be a user identifier. + +```java +// use in-memory caching with Flagsmith defaults and project level caching enabled +final String projectLevelCacheKey = "some-key-to-avoid-clashing-with-user-identifiers"; +final FlagsmithClient flagsmithClient = FlagsmithClient.newBuilder() + .setApiKey("FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY") + .withConfiguration(FlagsmithConfig + .newBuilder() + .baseURI("https://flagsmith.example.com/api/v1/") + .build()) + .withCache(FlagsmithCacheConfig + .newBuilder() + .enableEnvLevelCaching(projectLevelCacheKey) + .build()) + .build(); + +// if you need to access the cache directly, you can do this: +final FlagsmithCache cache = flagsmithClient.getCache(); +// invalidate project level cache +cache.invalidate(projectLevelCacheKey); +// check if project level flags have been cached +final FlagsAndTraits flags = cache.getIfPresent(projectLevelCacheKey); +``` + + + + +The `cache` option in the `Flagsmith` constructor accepts a cache implementation. This cache must implement the +{/* prettier-ignore */}FlagsmithCache +interface. + +For example, this cache implementation uses Redis as a backing store: + +```typescript +import { Flagsmith, Flags } from 'flagsmith-nodejs'; +import type { BaseOfflineHandler, EnvironmentModel, FlagsmithCache } from 'flagsmith-nodejs'; +import * as redis from 'redis'; + +const redisClient = redis.createClient({ + url: 'localhost:6379', +}); + +const redisFlagsmithCache = { + async get(key: string): Promise { + const cachedValue = await redisClient.get(key); + if (cachedValue) { + return new Flags(JSON.parse(cachedValue)); + } + }, + async set(key: string, value: Flags): Promise { + await redisClient.set(key, JSON.stringify(value), { EX: 60 }); + }, +} satisfies FlagsmithCache; + +const flagsmith = new Flagsmith({ + environmentKey: 'ser...', + cache: redisFlagsmithCache, +}); +``` + + + + +```php +$flagsmith = (new Flagsmith("FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY")); +// This will load the environment from cache (or API, if cache does not exist.) +$flagsmith->updateEnvironment(); +``` + +It is recommended to use a psr simple-cache implementation to cache the environment document between multiple requests. + +```sh +composer require symfony/cache +``` + +```php +$flagsmith = (new Flagsmith("FLAGSMITH_SERVER_SIDE_ENVIRONMENT_KEY")) + ->withCache(new Psr16Cache(new FilesystemAdapter())); +// Cache the environment call to reduce network calls for each and every evaluation. +// This will load the environment from cache (or API, if cache does not exist.) +$flagsmith->updateEnvironment(); +``` + +An optional cron job can be added to refresh this cache at a set time depending on your choice. Please set +EnvironmentTTL value for this purpose. + +```php +// the environment will be cached for 100 seconds. +$flagsmith = $flagsmith->withEnvironmentTtl(100); +$flagsmith->updateEnvironment(); +``` + +```sh +* * * 1 40 php index.php # using cli +* * * 1 40 curl http://localhost:8000/ # using http +``` + +Note: + +- For the environment cache, please use the server key generated from the Flagsmith Settings menu. The key's prefix is + `ser.`. +- The cache is important for concurrent requests. Without the cache, each request in PHP is a different process with its + own memory objects. The cache (filesystem or other) would enforce that the network call is reduced to a file system + one. + + + + +## Logging + +The following SDKs have code and functionality related to logging. + + + + +Logging is disabled by default. If you would like to enable it then call `.enableLogging()` on the client builder: + +```java +FlagsmithClient flagsmithClient = FlagsmithClient.newBuilder() + // other configuration as shown above + .enableLogging() + .build(); +``` + +Flagsmith uses [SLF4J](http://www.slf4j.org) and we only implement its API. If your project does not already have SLF4J, +then include an implementation, i.e.: + +```xml + + org.slf4j + slf4j-simple + ${slf4j.version} + +``` + + + + +## Contribute to the SDKs + +All our SDKs are Open Source. + + + + +https://github.com/Flagsmith/flagsmith-python-client + + + + +https://github.com/Flagsmith/flagsmith-java-client + + + + +https://github.com/Flagsmith/flagsmith-dotnet-client + + + + +https://github.com/Flagsmith/flagsmith-nodejs-client + + + + +https://github.com/Flagsmith/flagsmith-ruby-client + + + + +https://github.com/Flagsmith/flagsmith-php-client + + + + +https://github.com/Flagsmith/flagsmith-go-client + + + + +https://github.com/Flagsmith/flagsmith-rust-client + + + + +https://github.com/Flagsmith/flagsmith-elixir-client + + +