Until now, the only install paths were npm install -g flaglint or npx flaglint@latest. Both work fine — but both require Node.js 20 or newer. That’s a reasonable assumption for application developers, but it’s a friction point in a few common situations.
DevOps and platform engineers often run tooling audits across repos without a Node.js environment set up. Installing Node just to run one CLI is the kind of thing that gets a tool skipped in favour of whatever’s already available.
Docker-based CI is the bigger one. A lot of teams run their CI in minimal images — no Node, no npm. Adding a Node install step purely to run npx flaglint adds 30-60 seconds to every pipeline run and pulls in a dependency that has nothing to do with the actual application. With Homebrew, a Linux CI job can install FlagLint in a single brew install call with no Node dependency.
Mac developers who use Homebrew for CLI tools now get brew upgrade flaglint like any other tool, without thinking about npm.
The tap lives at github.com/flaglint/homebrew-tap. The formula fetches the published npm tarball directly from the npm registry and wires up the CLI binary.
The formula updates automatically on every release — a GitHub Actions job in the main repo runs after each npm publish, computes the new tarball SHA256, and commits an updated formula to the tap. So brew upgrade flaglint will always pull the current release without any manual intervention.
The OpenFeature ecosystem is a directory maintained by the OpenFeature project (a CNCF incubating standard) where providers, SDKs, hooks, and integrations can be listed. It is not an award or a certification. It is a discovery page. Teams that are already evaluating OpenFeature — researching providers, looking for tooling, trying to understand the ecosystem — land there.
Being listed means that those teams will now find FlagLint when they filter for integrations. That matters because the people who need FlagLint most are exactly the people who are actively thinking about OpenFeature.
The OpenFeature standard is the reason FlagLint exists. The whole problem FlagLint solves — the argument-order inversion between LaunchDarkly’s boolVariation(key, ctx, default) and OpenFeature’s getBooleanValue(key, default, ctx) — only surfaces when you are trying to move to OpenFeature. If teams weren’t adopting OpenFeature, there would be no migration to get wrong.
So it made sense to be in the directory where those teams are looking. Not to market FlagLint as a product, but to be findable at the point in the journey where someone is asking “what tooling exists around OpenFeature for LaunchDarkly migration?”
FlagLint is not an OpenFeature SDK or provider. It doesn’t evaluate flags. What it does is sit at the boundary between your existing LaunchDarkly codebase and the OpenFeature world you’re moving toward.
Specifically:
Audit — inventory every direct LaunchDarkly SDK call, classify each one by migration risk, produce a readiness score
Migrate — preview and apply proven-safe call-site rewrites that transpose arguments correctly and rename methods atomically
Validate — enforce in CI that no new direct LaunchDarkly calls land once you’ve drawn the boundary
None of that requires a network connection, an API key, or access to your LaunchDarkly environment. It’s all static analysis on your source code, running locally.
If you’re in the process of moving to OpenFeature and want to understand your current exposure before touching any code, the audit command is the right starting point.
Terminal window
npxflaglint@latestaudit./src
It runs in under a minute on most codebases and doesn’t touch any files.
Flipt is an open-source feature flag platform — one of the most popular self-hosted alternatives to LaunchDarkly. Their official documentation includes a guide for migrating a Node.js application from the LaunchDarkly SDK to OpenFeature, and that guide now recommends FlagLint for the codebase analysis step.
Here’s what that looks like in practice and why it makes sense.
Flipt supports OpenFeature through its official provider — so a team moving away from LaunchDarkly can route flag evaluation through the OpenFeature API to Flipt’s provider instead. The flag evaluation backend changes, but the application code uses the same vendor-neutral API regardless of which provider is behind it.
The challenge isn’t the provider setup. It’s the application code. A typical Node.js service has dozens of direct LaunchDarkly SDK calls — boolVariation, stringVariation, jsonVariation — all using LaunchDarkly’s argument order. OpenFeature’s equivalent methods take arguments in a different order, and a naive find-and-replace will silently swap your fallback values and context in every call site.
That’s the problem FlagLint solves before you write a single line of migration code.
Flipt’s guide recommends running three FlagLint commands as an optional but practical first step for larger codebases:
Terminal window
# See every direct LaunchDarkly call site in your codebase
npxflaglintscan./src
# Get a migration readiness score and per-flag risk classification
npxflaglintaudit./src
# Preview the exact rewrites FlagLint will make, without touching any files
npxflaglintmigrate./src--dry-run
The dry-run output shows you exactly which call sites will be rewritten and what the rewritten code looks like — before anything changes. Call sites that FlagLint cannot safely rewrite (dynamic flag keys, detail methods, bulk evaluation) are reported for manual review and left untouched.
After reviewing the diff, --apply writes the changes. Then you wire in the Flipt OpenFeature provider, run your tests, and enforce the boundary in CI with flaglint validate.
FlagLint and Flipt don’t overlap. FlagLint analyzes and rewrites your application’s call sites. Flipt evaluates your flags at runtime through an OpenFeature provider. They’re doing completely different jobs at different layers of the stack.
What they share is a user: someone who has decided to move away from vendor lock-in and is doing the actual migration work. FlagLint handles the static analysis half; Flipt handles the runtime half.
If you’re in that position — evaluating Flipt as a LaunchDarkly replacement and trying to understand the migration scope — flaglint audit is a good first step. It gives you a concrete inventory and readiness score before you commit to any approach.
Terminal window
npxflaglint@latestaudit./src
No API key, no source upload, runs locally in under a minute. See the full migration guide for everything that comes after the audit.
Most LaunchDarkly to OpenFeature migrations start the same way: someone opens a PR
with a find-and-replace across the codebase, the tests pass, the PR merges, and
two days later a subset of users hits the wrong feature state in production.
The root cause is almost always the same one-line trap — and this guide shows you
how to avoid it entirely, using a workflow that audits first, rewrites only what
it can prove is safe, and enforces the boundary in CI when you’re done.
TypeScript’s type system enforces interface contracts and catches argument-type mismatches at compile time — but it cannot see which of your modules still depend on the LaunchDarkly SDK, which of those call sites can be automatically rewritten, or how many engineer-hours the migration backlog represents.
Feature flag technical debt in TypeScript codebases compounds quietly. A team ships a boolean flag behind ldClient.boolVariation, the rollout succeeds, and the code moves on. Six months later the flag is still evaluated on every request. The surrounding code has grown around it. The LaunchDarkly SDK is a locked-in transitive dependency for the entire module that contains it. And no one has a reliable count of how many of these exist, because the only tool most teams reach for is a grep that conflates static flag keys, wrapper functions, and bulk calls into one undifferentiated list.
FlagLint is a free open-source CLI that parses TypeScript source files using an AST scanner to enumerate every LaunchDarkly SDK call site, classify each one by call type and risk, compute a readiness score, and output a migration plan to OpenFeature. No LaunchDarkly API key required.
Why grep misses TypeScript feature flag technical debt
Running grep -r "ldClient" ./src gives you a count. It does not give you a classification. Every result looks equivalent in grep output, but three structurally different situations hide behind the same pattern:
Static flag key, direct call — the flag key is a string literal; the call type is boolVariation, stringVariation, or numberVariation; the return type is known. FlagLint can generate a safe rewrite for this automatically.
Wrapper function with a dynamic key — the function accepts a flagKey parameter and calls the LaunchDarkly SDK internally. FlagLint cannot statically determine which flag is being evaluated, verify the call type, or confirm the return type. This is a high-risk call type.
Detail evaluation or bulk call — boolVariationDetail and allFlagsState have no direct OpenFeature provider equivalent and cannot be safely transformed by a static rewriter.
All three groups require completely different migration approaches — but grep cannot distinguish them.
Run flaglint scan against your source directory to get the AST-based inventory:
Terminal window
npxflaglintscan./src
Real output from the enterprise checkout service included with FlagLint (5 source files):
Seven of the nineteen usages resolve to a dynamic flag key. All seven originate from flags-wrapper.ts, which accepts flagKey as a parameter and proxies calls to the LaunchDarkly SDK. Grep would list those seven as equivalent entries alongside the statically-keyed calls in checkout.ts and pricing.ts. The AST scanner surfaces the wrapper boundary.
> (test/spec/mock files, deprecated/old/legacy directories), and minFileCount threshold.
> Git-history-based staleness (last evaluation date) requires git metadata and is not
> available in a pure static scan.
## Migration Readiness
Migration readiness: **67/100** · moderate
[█████████████████░░░░░░░░] 67%
18 safely automatable · 9 require manual review
The readiness score is the fraction of direct LaunchDarkly SDK call sites that FlagLint can rewrite automatically. A score of 67 means 18 of the 27 call sites are safely transformable. The remaining 9 require a human to resolve before an automated pass can run on those files.
The staleness signal column surfaces flag keys whose names carry heuristic staleness signal — keywords like old, deprecated, legacy, or tmp in the flag key itself. Zero here means no staleness signal at the source level. Staleness detection does not require a LaunchDarkly API key or runtime data.
Add --effort-estimate to convert the count into a planning number:
Terminal window
npxflaglintaudit./src--effort-estimate
This appends a three-phase estimate: automatable call sites at approximately 0.25 engineer-hours each, manual review call sites at 1.5–3 hours each, plus 30% overhead for validation and testing. Supplying --hourly-rate 150 appends a dollar range to the summary. The estimate is a planning heuristic calibrated to call-site complexity, not a billing projection.
Every flag key in the audit report lands in one of three tiers:
High risk — cannot be automated:
<dynamic key> (7 usages across 3 files) — the flag key is a runtime variable, not a string literal. FlagLint marks every dynamic flag key as high risk because it cannot statically determine which flag is being evaluated, verify the call type, or confirm the return type. The resolution is to trace back to the call sites that supply the key parameter, then extract each unique flag key to a named constant. Re-running flaglint audit after that change will reclassify the previously-dynamic entries as automatable.
checkout-experiment (1 usage) — boolVariationDetail is a detail evaluation call type. OpenFeature has a getBooleanDetails equivalent, but the reason vocabulary differs: the LaunchDarkly SDK returns TARGETING_MATCH and RULE_MATCH; OpenFeature uses its own reason strings. Code that inspects reason.kind or reason.ruleId must be updated by hand alongside the call site.
* (1 usage) — allFlagsState is a bulk call with no OpenFeature provider equivalent. The resolution is to enumerate the specific flag keys the bulk call feeds and replace them with individual named-key calls. If full flag state at application startup is genuinely required, retain the LaunchDarkly SDK client for that bootstrap path while migrating all other call sites.
Medium risk — automatable with review:
discount-config and pricing-tier-config are jsonVariation call types. They are safely automatable, but OpenFeature’s object value API returns unknown. After the rewrite, confirm that any code that casts or destructures the return value still compiles and behaves correctly.
Automatable — safe to transform:
Eight flag keys — checkout-v2, payment-provider, one-click-checkout, checkout-currency, discount-percentage, max-discount-amount, recommendations-variant, and bulk-discount-enabled — are called with boolVariation, stringVariation, or numberVariation using static string literal flag keys. FlagLint can rewrite all of these.
The automatable rewrite is not a text substitution. The LaunchDarkly SDK and OpenFeature provider place the fallback value and evaluation context in different argument positions:
The flag key is identical. The fallback value and evaluation context swap positions. A naive find-and-replace migration that does not track argument order evaluates every flag with the wrong context on the first request and returns the wrong result silently. FlagLint’s AST rewriter moves all three arguments to the correct positions for each automatable call type.
Preview every transformation before any file is touched:
Terminal window
npxflaglintmigrate--dry-run./src
The dry-run output shows a reviewable diff for each automatable call site alongside the OpenFeature provider setup steps. No files are modified.
Applying the migration plan and enforcing the boundary
Once you have reviewed the dry-run output and set up the OpenFeature provider:
Terminal window
npxflaglintmigrate--apply./src
This applies all safe rewrites in-place. Run your test suite after the apply. Then lock the boundary in CI:
Terminal window
npxflaglintvalidate--no-direct-launchdarkly./src
flaglint validate exits non-zero when any direct LaunchDarkly SDK call is detected. Add it to your GitHub Actions workflow and direct LaunchDarkly SDK calls become a build failure from that point forward, blocking regressions as the migration lands across multiple PRs.
Work through the high-risk items in batches. After each batch, re-run flaglint audit to watch the readiness score climb. At 80 or above, the remaining feature flag technical debt in TypeScript can be handled in a single automated pass — flaglint migrate --apply clears it and flaglint validate --no-direct-launchdarkly confirms the boundary is clean.
The audit plus the CI gate is a closed loop: audit measures what exists, migrate rewrites what is safe, validate blocks regressions, audit confirms progress. You can run the full cycle on a large codebase before writing a single line of migration code. The readiness score tells you up front whether you are looking at a two-sprint effort or a six-month program, and the migration plan tells you exactly which call sites require which kind of attention.