Docs

Get started with featflags

Add an SDK, give it a key, and read your first flag. Each environment in the app has two kinds of key: client keys (pk_) for browsers and mobile apps, and server keys (sk_) for your servers, which you keep secret.

SDKs

Every SDK evaluates flags in your own process, from a copy it keeps up to date, so your users’ attributes never leave it and the same user gets the same answer in every language.

TypeScript

Browsers, Node, Cloudflare Workers and Vercel Edge. Zero dependencies, about 12 KB gzipped.

Install

npm install @featflags/js

Use

import { FlagClient } from '@featflags/js'

const flags = new FlagClient({
  key: 'pk_live_…',
  cdnUrl: 'https://flags.featflags.io',
  context: { kind: 'user', key: userId, plan: 'pro' },
})

if (flags.bool('new-checkout', false)) showNewCheckout()

React

Provider and hooks, with server rendering.

Install

npm install @featflags/react

Use

import { FeatflagsProvider, useFlag } from '@featflags/react'

<FeatflagsProvider options={{ key: 'pk_live_…', cdnUrl: 'https://flags.featflags.io' }} context={{ key: user.id }}>
  <App />
</FeatflagsProvider>

function Checkout() {
  return useFlag('new-checkout', false) ? <NewCheckout /> : <OldCheckout />
}

React Native

React Native 0.74+, with the React hooks. Starts from the last flags it saw, even offline.

Install

npm install @featflags/react-native @react-native-async-storage/async-storage
npm install @react-native-community/netinfo   # optional: refetch as soon as the network is back
cd ios && pod install

Use

import { createClient, FeatflagsProvider, useFlag, type FlagClient } from '@featflags/react-native'

const flags = createClient({ key: 'pk_live_…', cdnUrl: 'https://flags.featflags.io' })

export function App() {
  const [client, setClient] = useState<FlagClient>()
  useEffect(() => void flags.then(setClient), [])
  if (!client) return null
  return <FeatflagsProvider client={client} context={{ key: user.id }}><Home /></FeatflagsProvider>
}

function Checkout() {
  return useFlag('new-checkout', false) ? <NewCheckout /> : <OldCheckout />
}

Go

Standard library only. OpenFeature provider in its own module.

Install

go get github.com/featflags/featflags-go

Use

flags, err := featflags.New(ctx, featflags.Config{
	Key:    "sk_live_…",
	CDNURL: "https://flags.featflags.io",
})
user := featflags.NewContext(map[string]any{"key": userID, "plan": "pro"})
if flags.Bool(ctx, "new-checkout", user, false) { … }

Python

Standard library only. OpenFeature provider behind an extra.

Install

pip install featflags
pip install 'featflags[openfeature]'   # with the OpenFeature provider

Use

flags = featflags.Client("sk_live_…", "https://flags.featflags.io")

if flags.bool_variation("new-checkout", {"key": user_id, "plan": "pro"}, False):
    ...

Java

Java 17+ and Kotlin on servers, Jackson only. OpenFeature provider included.

Install

implementation("io.featflags:featflags-java:0.1.0")

Use

var flags = FeatflagsClient.builder("sk_live_…", "https://flags.featflags.io").build();

if (flags.boolVariation("new-checkout", Context.of(Map.of("key", userId)), false)) { … }

Rust

Async, with an on-disk cache so your service starts even when we can’t be reached.

Install

cargo add featflags

Use

let flags = featflags::Client::builder("https://flags.featflags.io", "sk_live_…")
    .build()
    .await;
let ctx = featflags::context(json!({ "key": user_id, "plan": "pro" }));
if flags.bool("new-checkout", &ctx, false) { … }

.NET

.NET 8+, base class library only. OpenFeature provider included.

Install

dotnet add package FeatFlags

Use

await using var flags = await FeatFlagsClient.CreateAsync(new FeatFlagsOptions
{
    Key = "sk_live_…",
    CdnUrl = "https://flags.featflags.io",
});
var user = Context.Of(new Dictionary<string, object?> { ["key"] = userId, ["plan"] = "pro" });
if (flags.BoolVariation("new-checkout", user, false)) { … }

Ruby

Standard library only. Works under Puma and Unicorn without extra setup.

Install

bundle add featflags

Use

flags = Featflags::Client.new("sk_live_…", "https://flags.featflags.io")

if flags.bool_variation("new-checkout", { key: user.id, plan: "pro" }, false)
  # …
end

PHP

PHP 8.2+. A request never waits for us once your servers have the flags.

Install

composer require featflags/featflags

Use

$flags = new Featflags\Client('sk_live_…', 'https://flags.featflags.io');

if ($flags->boolVariation('new-checkout', ['key' => $userId, 'plan' => 'pro'], false)) {
    // …
}

Elixir

Add it to your supervision tree. Flags are read in the calling process.

Install

{:featflags, "~> 0.1"}   # in mix.exs deps

Use

children = [
  {Featflags, name: MyApp.Flags, key: "sk_live_…", cdn_url: "https://flags.featflags.io"}
]

if Featflags.bool_variation(MyApp.Flags, "new-checkout", %{key: user.id}, false) do
  # …
end

iOSSwift

Swift, for iOS and macOS. Starts from the last flags it saw, even offline.

Install

.package(url: "https://github.com/featflags/featflags-swift", from: "0.1.0")   // or in Xcode: File → Add Package Dependencies

Use

var options = FlagClient.Options(key: "pk_live_…", cdnURL: URL(string: "https://flags.featflags.io")!)
options.context = ["key": user.id, "plan": "pro"]
let flags = FlagClient(options)

if flags.bool("new-checkout", default: false) { … }

AndroidKotlin

Kotlin, minSdk 24. Starts from the last flags it saw, even offline.

Install

implementation("io.featflags.kotlin:featflags-android:0.1.0")

Use

val flags = FeatflagsClient.android(app, FeatflagsOptions(key = "pk_live_…", cdnUrl = "https://flags.featflags.io"))

flags.identify(Context.user(user.id, mapOf("plan" to "pro")))
if (flags.bool("new-checkout", false)) { … }

FlutterDart

Dart, for Flutter apps, Flutter web and Dart programs. Widgets rebuild when a flag changes.

Install

flutter pub add featflags_flutter
dart pub add featflags   # a Dart program without Flutter

Use

final flags = FlagClient(FlagClientOptions(
  key: 'pk_live_…',
  cdnUrl: Uri.parse('https://flags.featflags.io'),
  context: {'key': user.id, 'plan': 'pro'},
));
runApp(FeatFlagsProvider(client: flags, child: const App()));

// In build:
FeatFlagsProvider.of(context).boolValue('new-checkout', false) ? const NewCheckout() : const Checkout()

OpenFeature

The Go, Python, Java, .NET, Ruby, PHP, Elixir, iOS, Android and Flutter SDKs come with an OpenFeature provider. Code written against the OpenFeature API reads featflags flags once you register it, so you can switch to featflags, or away from it, without touching the call sites.

AI assistants (MCP)

Connect Claude, Cursor or another assistant to the hosted MCP server and it can look up, explain, create and change your flags. There’s nothing to install and no key to copy: add the URL, sign in with your featflags account when the assistant asks, then pick the organization it works in and whether it may change things or only read them.

https://app.featflags.io/mcp
  • Claude Code

    Run this, then /mcp in Claude Code to sign in:

    claude mcp add --transport http featflags https://app.featflags.io/mcp
  • Claude

    On claude.ai or in the desktop app: Settings → Connectors → Add custom connector, and paste the URL.

  • Cursor

    Settings → MCP → Add new MCP server, or in ~/.cursor/mcp.json:

    { "mcpServers": { "featflags": { "url": "https://app.featflags.io/mcp" } } }
  • VS Code

    Command Palette → MCP: Add Server → HTTP, or in .vscode/mcp.json:

    { "servers": { "featflags": { "type": "http", "url": "https://app.featflags.io/mcp" } } }

The assistant acts as you, so its changes need the same comments and approvals as yours and show up in each flag’s history. Disconnect it any time under Connected apps on your account page.

CLI

The featflags command works with projects, flags, their history and uploaded customer lists from a terminal or CI. featflags login signs you in through the app in your browser; in CI, set FEATFLAGS_API_URL to https://app.featflags.io and FEATFLAGS_TOKEN to an API token from Settings → API tokens instead. FEATFLAGS_PROJECT saves repeating --project.

featflags login --api-url https://app.featflags.io   # opens the app to pick an organization
featflags flags list --project acme
featflags flags toggle new-checkout --project acme --env production --off --comment "incident 4211"
featflags lists upload customers.txt --project acme --segment customers --list included

Terraform

The Terraform provider manages projects, environments, SDK keys, flags and their targeting rules, segments, release templates and webhooks as code. It signs in with an API token, whose roles decide what it can change. Rules you add in the app live alongside the ones Terraform manages, and an environment that needs approval holds Terraform’s changes until someone approves them.

terraform {
  required_providers {
    featflags = { source = "featflags/featflags" }
  }
}

provider "featflags" {
  api_url = "https://app.featflags.io"   # the token comes from FEATFLAGS_TOKEN
}

Importing from LaunchDarkly

The CLI copies a LaunchDarkly project into featflags: environments, context kinds, flags with their targeting, segments and big segments. Bucketing is compatible, so every user keeps the variant they had. Start with --dry-run to see what it would do, then import and pass --verify a file of sample contexts, one JSON object per line, to check that every flag gives the same answer as LaunchDarkly. Running it again only writes what changed, so you can keep both in step while you switch SDKs.

export LD_API_TOKEN=api-…   # a LaunchDarkly access token that can read the project
featflags import launchdarkly --ld-project acme --to-project acme --dry-run
featflags import launchdarkly --ld-project acme --to-project acme --verify contexts.jsonl

Experiments

An experiment splits part of a flag’s traffic between its variants and shows which did better on the metrics you choose. First generate the environment’s experiment key in its settings: it’s created in your browser and shown once, for your servers to hold, and we never see it, so the IDs your SDKs report can’t be traced back to a person without it. Then add metrics on the project’s Metrics page, send the events they count with your SDK’s track(), and start the experiment from a rule or the default in a flag’s targeting with Run an experiment.

Release templates

A release template is a rollout you write once and apply to any flag: staff first, then 10%, 50% and everyone, each step started by hand or after a set time. Create one on the project’s Release templates page, starting from the Gradual or Staff first preset, and apply it from a flag’s Release tab. On Team and Business, a release can also watch your metrics while each step is live, and pause or go back a step when the new variant does worse.