Feature flags, rollouts and experiments

Feature flags for the whole team.

Turn features on for some users and off for others without a deploy. Changes reach your servers in about 5 seconds, and every plan has unlimited seats and flags.

  • No per-user pricing
  • Free plan, no card
  • Imports from LaunchDarkly
The featflags flag list: seven flags with on/off toggles and what each serves in production, such as a 25% rollout and a 50/50 split by organisation.
for a change to reach your servers
~5 s
seats and flags, on every plan
Unlimited
SDKs, for browsers, servers and mobile apps
14
of your users’ attributes sent to us
None

Why featflags

What you get on every plan

  • Our outages don’t take your flags down

    If featflags has a problem, your apps keep serving the flags they already have. You can’t change a flag until we’re back, but production carries on as it was.

  • Your users stay yours

    Your users’ names, emails and attributes never reach us, on any plan. SDKs report how often each variant was served, and in an experiment who saw which variant under IDs that only you can link back to a person.

  • Priced on usage, not people

    Invite the whole company and create as many flags as you need. We don’t charge per monthly active user. Paid plans include a generous amount of traffic and a fair per-million rate above it.

  • An easy move from LaunchDarkly

    Contexts, individual targets, prerequisites and segments work the way you know them. The importer keeps every user in the variant they had, and checks the result against LaunchDarkly’s own SDK.

Getting started

From sign-in to your first live flag

  1. 1

    Sign in

    Use Google or GitHub. You get an organisation on the Free plan and can invite your team straight away.

  2. 2

    Add an SDK

    Create a flag, copy a key and add a few lines to your app. The setup checks that your first evaluation arrived.

  3. 3

    Change it live

    Flip the flag or edit a rule in the dashboard. Your servers have the change in about 5 seconds.

The product

What’s in the dashboard

Targeting

Target people, organisations and segments

Pick users or organisations by key, write rules on any attribute, make one flag depend on another, and reuse segments across flags. For entitlements and betas, upload a list of customer IDs, millions long if you need.

  • Contexts with kinds and multi-contexts
  • Regex, semver and date operators
  • Test any context before you publish
The featflags flag page: a prerequisite on new-billing, individual targets for two users and one organisation, and the start of a rule list.

Rollouts

Percentage rollouts on a schedule

Split traffic by percentage and set dates for the next steps. People stay in their bucket, so going from 20% to 50% only adds users. On paid plans you can also require an approval in production or schedule a change for Monday morning.

  • Time-based ramps
  • Approvals and scheduled changes
  • Kill-switch URLs for your alerting
A percentage rollout: 5% on now, a first step at 20% already in effect, and a second step to 50% scheduled for next month.

History

Who changed what, and why

Every change records its author, comment and a field-by-field diff, and you can restore any earlier version. Require comments in production and send changes to Slack or your own webhooks.

  • Restore with a preview
  • Webhooks and Slack
  • Roles per environment, scoped tokens
The project history: each change with its flag, environment, author, comment and a Restore button.

Beyond on and off

Test, roll out and hand over the busywork

All three are on every plan. Free runs one experiment at a time, Team 20 and Business as many as you like, with bandits.

  • Experiments

    Find out which variant wins

    Run an A/B test on any flag and measure what matters to you, such as completed checkouts. Results are safe to read at any time, so looking early doesn’t fool you, and a bandit can move traffic to the winner while the test runs. Experiment data uses IDs that only you can link back to a person.

    • Bayesian or frequentist results
    • Guardrail metrics that call out harm
    • A warning when the split looks off
  • Release templates

    Roll out every flag the same way

    Write a rollout plan once, such as staff first, then 10%, 50% and everyone, and use it on any flag. Start each step yourself, or let the next one start on its own a day later. On Team and Business it can also watch your metrics and pause, or step back, when the new variant does harm. Every step goes through your approvals and shows in history.

    • Gradual and Staff first to start from
    • Steps that start on their own on Team and Business
    • Guardrail metrics that pause or roll back a release
  • AI assistants

    Let your AI assistant do the flag work

    Connect Claude, Cursor or another AI assistant to featflags and ask it to create a flag, roll one out or find the ones ready to clean up. It acts as you, with your access or only read access, and its changes still need the comments and approvals yours do.

    • Any assistant that supports remote MCP servers
    • Sign in once, no keys to copy
    • Disconnect it from your account at any time

Use cases

What teams use it for

  • Kill switches

    Turn off a broken feature in seconds, by hand or from your alerting tool with a trigger URL.

  • Gradual rollouts

    Ship dark, open it to staff, then go to 1%, 10% and everyone on a schedule.

  • Betas and entitlements

    Gate features by plan, organisation or an uploaded list of customer IDs.

  • Leaving LaunchDarkly

    Import flags, segments and big segments, then check that every evaluation matches.

SDKs

SDKs for the web, your servers and your apps

Every SDK gives the same answer for the same user, so a flag behaves the same in your Go API, your React app and your iOS app.

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

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()

Provider and hooks, with server rendering.

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 0.74+, with the React hooks. Starts from the last flags it saw, even offline.

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 />
}

Standard library only. OpenFeature provider in its own module.

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) { … }

Standard library only. OpenFeature provider behind an extra.

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

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

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

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

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

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

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 8+, base class library only. OpenFeature provider included.

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)) { … }

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

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

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

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

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

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

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

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

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

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) { … }

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

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)) { … }

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

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 providers
  • Terraform provider
  • OpenTelemetry hooks
  • CLI
  • MCP server
  • Webhooks & Slack
  • Google & GitHub sign-in
  • Single sign-on (SAML, OIDC)

Limits

Two things to know before you start

Changes take seconds

Servers have a change within about 5 seconds. Browsers and mobile apps take up to a minute, or less when a tab or the app comes back into view. If you need every client updated in under a second, we’re not the right fit yet.

Browser and app flags are visible to your users

Only flags you mark client-visible reach a browser or mobile app, and anyone who inspects your app can read their rules. Keep sensitive targeting in server-only flags. Target keys are hashed, and private lists need a token your server issues.

Questions

Frequently asked questions

Something else? The docs cover every SDK and tool.

How fast does a change reach my users?

Servers have it within about 5 seconds. Browsers and mobile apps pick it up within a minute, or as soon as a tab or the app comes back into view.

Can people see my flags in the browser?

Only the flags you mark client-visible are available in browsers and mobile apps, and anyone who inspects your app can read their rules. Keep anything sensitive in server-only flags. Target keys are hashed, and private lists need a token your server issues, so nobody can list who’s on them.

What does it cost?

Prices aren’t published yet. Every plan will have unlimited seats and flags and no per-user pricing. Free has fixed limits; paid plans include a set amount of traffic each month and charge per million above it.

What do SDKs send you about my users?

Nothing about who they are. SDKs send us counts of which variant was served, never your users’ names, emails or other attributes. For a flag in an experiment they also send which variant each user saw and the events you track, under an ID made with a key that only you hold, so only you can link it back to a person. Those rows are deleted 30 days after the experiment ends.

Can an AI assistant break something?

It can only do what you can, or only read if you choose that when you connect it. Its changes are recorded as yours, need a comment where you require one and wait for reviewers where you require approval. It can’t manage members, tokens, SDK keys or billing.

Can I move from LaunchDarkly without changing who sees what?

Yes. featflags supports LaunchDarkly-compatible bucketing, so users keep their variants. The importer brings flags, targets, rules, segments and big segments across, and --verify checks the evaluations against LaunchDarkly’s own SDK.

Does it work with OpenFeature?

Yes. There are OpenFeature providers for Go, Python, Java, .NET, Ruby, PHP, Elixir, iOS, Android and Flutter, and every SDK supports the same hooks.

Create your first flag

Sign in with Google or GitHub. The Free plan doesn’t need a card.