The open-source LaunchDarkly alternative: feature flags in your repo
The main open-source LaunchDarkly alternatives are dif, GrowthBook, Flagsmith, and Unleash. All four keep feature flags out of a proprietary dashboard, but they split on where the flags actually live. dif puts them in your repo as Markdown files, reviewed in a pull request and versioned in git. The others run a service or a self-hosted dashboard that your app talks to.
Most people come looking for the same reason. Flags in a hosted dashboard drift away from the code they gate: nobody remembers why new-checkout-v2 exists or whether it is safe to delete, so it sits at 100% for three years with a dead branch behind it. This post is about what you give up and what you gain by moving flags into the repo, and how dif compares to LaunchDarkly and the other open-source options.
Key Takeaways
- dif is the git-native option: one
.mdfile per flag or experiment, reviewed in a PR, no dashboard to reconcile against the code.- It is open source (MIT) and self-hostable. The CLI and SDK are free; dif Cloud is $50/month for 1 million events with no per-seat fees.
- The runtime SDK is about 5 kB with zero dependencies and makes no network call to pick a variant.
- The tradeoff: turning a flag off is a commit and a deploy, not a sub-second dashboard toggle.
- GrowthBook, Flagsmith, and Unleash are also open source, but each runs a separate service or dashboard. dif’s source of truth is the repo.
What LaunchDarkly does well
LaunchDarkly leads the category. It pushes flag changes to your app in real time, so you can flip a flag on or off from the dashboard and see it take effect in under a second, no deploy required. That instant kill switch matters on a payment path or anywhere a bad release needs to stop now. It ships SDKs for a long list of languages, has a mature targeting UI, and covers the enterprise checklist: role-based access, audit logs, approval flows, SSO, and support with an SLA.
If you need to disable a feature in under a second across a fleet, LaunchDarkly does that and dif does not. If your backend is Go, Python, Ruby, and Java as well as JavaScript, LaunchDarkly has an SDK for each today. Those are the cases where a hosted service earns its price.
The downsides are baked into that model. Pricing is seat-based and usage-based, so the bill grows with your team and your traffic. The flags live in a dashboard your repo cannot see, which is where the drift starts. And because it is closed source, self-hosting and auditing the internals are off the table.
What changes when flags live in the repo
dif starts from one opinion: a feature flag is part of your codebase, so it should live in your repo. Every flag and every experiment is a single Markdown file next to the code it changes.
---
id: new-checkout
status: active
owner: sam@acme.com
surface: checkout
audience:
include:
- device_type: [mobile, tablet]
variants:
- id: "off"
weight: 90
- id: "on"
weight: 10
metrics:
primary: completed_checkout
guardrails:
- refund_rate
---
## Brief
Ramp to 25% once the guardrails hold for a week.
You review that file in a pull request like any other change. Its history is the git history. When the flag is done, the decision and what you learned go in the same file. A flag and an A/B test are the same format: a flag is one you are ramping toward 100%, an experiment is one you hold at a split until the numbers answer a hypothesis.
The CLI does the checking and compiling. dif validate is a type checker for your flags: weights must total 100, the owner must be a real email, and it scans your application source for dif("...") call sites and warns when code points at a flag that is not in the repo. It runs in CI, so a broken flag fails the pull request like a broken build. dif build compiles the active files into a typed client your app imports, plus a context.json that summarizes every active flag and each surface’s latest learning.
In your code, the call site is one function:
import "./dif/generated/client";
import { dif } from "@dif.sh/sdk";
if (dif("new-checkout") === "on") {
return <NewCheckout />;
}
return <LegacyCheckout />;
Assignment is a pure function of the user id, so there is no network call to evaluate a flag and no assignment database. A user does not flicker between variants across page loads or devices. That context.json is the part a dashboard cannot give a coding agent: dif init writes guidance into CLAUDE.md and AGENTS.md, so Claude or Cursor reads which code paths are gated and what has already been tried, and can author a flag correctly on the first pass.
dif vs LaunchDarkly, side by side
| Dimension | dif | LaunchDarkly |
|---|---|---|
| Where flags live | Markdown files in your repo | Hosted dashboard |
| License | Open source (MIT) | Proprietary |
| Pricing | Free CLI and SDK; Cloud $50/mo for 1M events, no per-seat fees | Seat-based and usage-based |
| Runtime SDK | ~5 kB, zero dependencies, no network call to assign | SDK plus a streaming service |
| Change a flag | Commit and deploy | Instant, from the dashboard |
| Sub-second kill switch | No (rollback is a deploy) | Yes |
| Language SDKs | JavaScript and TypeScript, with React and Svelte adapters | Many languages |
| Review and audit trail | Pull requests and git history | In-app approvals and audit logs |
| Agent-readable state | Yes (context.json) | No |
| Self-hostable | Yes; the cloud is optional | SaaS |
LaunchDarkly wins two of those rows outright: the instant kill switch and the breadth of language SDKs. dif does not close that gap and does not pretend to. It wins the rest. The flags live in the repo instead of a dashboard, the CLI and SDK are free and open source, and the runtime is a few kilobytes with no service to call.
The other open-source alternatives
dif is not the only option, and the others are worth knowing before you choose.
GrowthBook is the best-known open-source experimentation tool. Its stats engine is strong and it reads from your data warehouse, which matters most when analysis is the priority. It runs as a separate app with its own database, so flags and results live in that app rather than in your repo.
Flagsmith and Unleash are open-source feature-flag servers. Both are solid and self-hostable, and both are built around a backend and a UI that your app calls. If you want a hosted flag service you can run yourself, they fit. If you want the flags reviewed in the same pull request as the code, they do not, because the flag state lives in the server, not the repo.
PostHog bundles flags and experiments into a product-analytics platform. That is a good fit if you want analytics, session replay, and flags in one place, and a heavier footprint than a focused flag tool if you do not.
dif makes a bet the others do not: the repo is the source of truth, one file format covers both flags and experiments, every concluded test writes a learning back to its surface, and the whole thing is readable by a coding agent. For a fuller breakdown, see the feature flags and experimentation pages.
The tradeoff
Rolling back a flag in dif means editing one number to 0 and merging. There is no separate rollback mechanism and no dashboard state that can disagree with the repo, so why you rolled back, and when, is in the history rather than in someone’s memory of a 2am toggle.
That is slower than a dashboard toggle. If your build takes ten minutes, your worst-case kill is ten minutes. For a payment path that needs a sub-second kill switch, dif is the wrong tool for that flag and LaunchDarkly is the right one. For most flags, where the failure mode is “some users see the new thing,” a merge and a deploy is fine, and every failure mode in dif resolves to the control variant and records nothing.
When to choose which
Choose dif if you want flags and experiments in the repo, reviewed in pull requests, on an open-source stack with no per-seat fees, and your stack is JavaScript or TypeScript. It fits engineering-led teams that already live in git and want experiments that do not rot in a dashboard.
Choose LaunchDarkly if you need sub-second kill switches on critical paths, SDKs across many backend languages, or a mature enterprise governance suite today, and the seat-based pricing is acceptable for that.
Choose GrowthBook, Flagsmith, or Unleash if you specifically want a self-hosted dashboard or warehouse-native stats, and you are comfortable running a separate service for it.
Try dif
Install the CLI, then scaffold a project. No account or API key required.
npm install -g @dif.sh/cli
dif init
Prefer a standalone binary? The install script and Homebrew both work:
curl -fsSL https://dif.sh/install.sh | sh
brew install dif-sh/tap/dif
dif init writes a dif/ directory, a generated TypeScript client, and the agent guidance files. From there, dif new drafts a flag, dif validate checks it, and dif build compiles it. The CLI reference and the SDK docs cover the rest, and dif Cloud pricing is here when you want hosted analysis. The code is on GitHub.
FAQ
Is there a real open-source alternative to LaunchDarkly? Yes. dif, GrowthBook, Flagsmith, and Unleash are all open source. dif is the git-native one, keeping flags in your repo as Markdown files rather than in a separate service.
Is dif free? The CLI and SDK are free and MIT licensed. dif Cloud, the optional hosted layer for analysis, is $50/month for 1 million events with unlimited seats, then $0.02 per 1,000 events. There are no per-seat fees.
Can I self-host dif? Yes. The core runs entirely from your repo with nothing hosted. dif Cloud is optional and self-hostable, and nothing in the core requires it.
How does dif handle a kill switch? By editing the flag’s weight to 0 and merging. That is a commit and a deploy, not an instant toggle. If you need sub-second kills on a critical path, use a streaming service like LaunchDarkly for that specific flag.
What languages does dif support? The SDK is JavaScript and TypeScript, with adapters for React and Svelte. It works in the browser and on the server. Broader backend-language coverage is where a tool like LaunchDarkly is still ahead.
How do I move a flag off a dashboard and into the repo? Recreate it as a .md file with the same variants and weights, gate the code path with a dif("flag-id") call, and run dif validate. Because the flag is now a file, the change goes through code review like anything else.