TL;DR
This guide replaces scattered environment-variable, hard-coded, JSON, and database flags with GrowthBook without a big-bang cutover. The agent inventories the legacy system, writes its semantic contract, puts a typed provider boundary in front of every read, compares GrowthBook decisions in shadow mode, transfers authority one flag at a time, and deletes the old paths only after the observation window closes. The key safety property is reversibility. Legacy mode remains available during migration, per-key cutover is allowlisted, mismatches are observable without leaking identities, and original cohort behavior is preserved or consciously changed. The finished state is one authoritative provider, tested fallbacks, documented ownership, and no hidden legacy path serving traffic. This guide is optimized for AI coding agents, and it is recommended that you hand it off to your agent of choice for implementation.Guide map
Give this guide to your coding agent
Copy this page’s URL and the prompt below into a coding agent that can inspect your repository. ReplaceREPLACE_WITH_REPOSITORY with the repository path or name and REPLACE_WITH_GUIDE_URL with this page’s public URL. Start in a branch, worktree, or disposable clone, and keep the legacy provider authoritative during the first pass.
Task
Replace an application-specific feature flag system with GrowthBook without changing production behavior in one large cutover. The legacy system may combine environment variables, hard-coded booleans, JSON files, a database table, account allowlists, or homegrown percentage rollouts. This guide performs the migration as observable, reversible changes. It inventories and classifies each switch, puts one typed interface in front of reads, compares GrowthBook in shadow mode, transfers control one flag at a time, and removes the old system only after it stops serving traffic. The reference Node.js/TypeScript service uses oneGrowthBookClient for feature definitions and request-scoped evaluators for isolated user attributes. Browser applications should use the JavaScript SDK or their framework SDK.
Use this guide when
- Your application already has feature-like conditions spread across environment variables, source files, configuration files, or database rows.
- Changing a runtime flag currently requires a deployment, a database edit, or a custom admin endpoint.
- You need targeting, gradual rollout, environment separation, a kill switch, an experiment, or a visible change history.
- You cannot accept a big-bang provider replacement.
- You can observe both legacy and GrowthBook decisions before GrowthBook serves them.
- You have a stable user, device, or account identifier for any percentage-based rule.
Do not use this guide when
- The value is a build-time constant that should change only when you build a new artifact.
- The value is a secret, credential, connection string, or cryptographic key.
- The condition grants authorization, enforces a billing entitlement, or protects data. A feature flag may hide a control or stage a feature, but the server must still enforce authorization and entitlement independently.
- The value is permanent application configuration with no release lifecycle. Keep it in typed configuration unless runtime targeting is a real requirement.
- A single static Boolean, owned by one developer, with no runtime changes or targeting, is genuinely the complete requirement. A small DIY switch may be the simpler system.
- The old implementation cannot be read safely in parallel. First create a read-only snapshot or a test environment where comparison cannot mutate state.
Tested stack
The complete TypeScript examples were checked against GrowthBook commite44a15af063860c7118f52508746356d55e5a91d, including @growthbook/growthbook 1.7.0 evaluation behavior and the SDK’s own feature, typed-feature, and multi-user tests. The reference commands assume Node.js 22, TypeScript 5.7, npm, and Jest 29.7.
Pin the SDK and CLI versions in your lockfile. Re-run the contract tests in this guide before adopting a newer major version or changing GrowthBook rule semantics.
Required access
You need:- Read access to the application repository, deployment configuration, and legacy flag table or configuration store.
- Permission to create a GrowthBook project, feature flags, attributes, and SDK connections for the environments in scope.
- A GrowthBook SDK client key for each deployed environment. SDK connection endpoints are read-only and intended for SDK delivery; do not confuse the client key with a REST API secret.
- A Secret Key or Personal Access Token only if you enable CLI type generation in CI. Store it as
GBCLI_BEARER_AUTH; never commit it or pass it to application code. - Access to production logs or metrics so you can measure shadow mismatches and fallback use.
- An owner who can decide whether assignment changes are acceptable for existing percentage rollouts.
Files this guide creates
Adapt the paths to your repository, but keep the provider boundary intact:End state
The migration is complete only when all of these statements are true:- Every legacy switch has a recorded owner, type, source, purpose, safe fallback, environment scope, and removal decision.
- Authorization, billing entitlements, secrets, and permanent configuration remain outside the feature flag system.
- All application reads go through one typed provider interface.
- GrowthBook uses the same attribute names, types, rule order, environment mapping, and intended randomization unit as the accepted migration contract.
- Shadow telemetry shows no unexplained decision mismatch for the contexts that matter.
- A known assignment change in a percentage rollout is either prevented or explicitly accepted before cutover.
- Production can return to the legacy provider through a deployment-level switch during the cutover window.
- A GrowthBook fetch failure produces the documented code fallback or temporary legacy fallback. It does not produce an accidental truthy value.
- Unit, targeting, failure, and parity tests pass in CI.
- The legacy mutation path is disabled before its data and code are deleted.
- Code references, stale review, ownership, and cleanup dates are part of the ongoing flag lifecycle.
Rollback
KeepFLAG_PROVIDER_MODE=legacy available until every migrated flag has completed its observation window. If a GrowthBook-served decision causes an incident:
- Set
FLAG_PROVIDER_MODE=legacyin the deployment configuration. - Restart or redeploy the affected service so the startup-only mode changes.
- Confirm the startup receipt reports
"mode":"legacy". - Confirm decision telemetry reports
"servedBy":"legacy"for the affected key. - Revert the GrowthBook feature revision if the issue is a rule or value error.
- Preserve mismatch and incident evidence before editing either implementation.
Understand what you are replacing
A DIY feature flag often starts as one line:
GrowthBook feature evaluation normally happens locally in the SDK after the feature payload has loaded. A flag check does not need a control-plane request every time. The migration still adds operational dependencies: payload delivery, SDK initialization, identity quality, and a publishing workflow. This guide makes those dependencies visible instead of treating them as magic.
Phase 0: Freeze unsafe changes
Do not migrate while the old system is changing underneath you. Establish a short change freeze for the first flag or require every legacy change to be mirrored in GrowthBook during shadow mode. Before editing code:- Cutover owner: The person who can change
FLAG_PROVIDER_MODEand redeploy. - Observation window: At least enough traffic to exercise the important targeting branches. Use time and traffic, not time alone.
- Mismatch budget: Zero unexplained mismatches for kill switches and high-risk release flags. A documented cohort change may be acceptable for low-risk rollouts.
- Fallback policy: The safe code value for each key and whether a temporary legacy fallback is allowed.
- Identity policy: The canonical user, anonymous device, and account identifiers.
- Mutation freeze: Who can change legacy values and how those changes are mirrored.
- Deletion gate: The exact evidence required before legacy code, rows, or admin endpoints can be removed.
Inventory every possible flag source
Start broad. Search results are candidates, not confirmed flags. Terms such asenabled, beta, or rollout also appear in tests, library code, and ordinary domain logic.
If rg is installed, run these searches from the repository root:
Add a repeatable inventory script
The following dependency-free script emits JSON Lines so you can diff findings across commits. It does not claim that every match is a flag. It gives the agent and reviewer a bounded queue for classification. Createscripts/inventory-feature-flags.mjs:
Inventory the database without changing it
Adapt this query to your schema. Select metadata and values only if your security policy allows them:Classify before you migrate
Put each confirmed candidate into exactly one primary class. Record secondary concerns in notes rather than inventing overlapping categories.Never migrate these as ordinary flags
Authorization: A client-visible value such asis-admin must not grant server access. Check the authenticated principal and permission on every protected operation.
Billing entitlements: A flag can control a rollout within the set of entitled accounts. It must not replace the billing or entitlement service. A user changing local attributes must not unlock a paid capability.
Secrets: SDK feature payloads are configuration delivery. Do not put API tokens, database passwords, encryption keys, private prompts, or credentials into a flag value.
Unreviewed executable content: Prefer a flag that selects a versioned implementation, such as recommendation-v2, over remotely editable JavaScript, SQL, or shell commands.
Safety invariants: Validation, rate limits, data retention, and destructive-operation safeguards belong in code and policy. Do not make them removable through an ordinary release flag.
Create the migration manifest
Use one row per flag. A spreadsheet, issue table, or version-controlled YAML file works during the migration. Do not include raw user or account IDs.Write the semantic contract
Two providers can both returntrue to a happy-path test while disagreeing for production users. Before creating GrowthBook rules, document how the legacy system makes a decision.
For every key, answer these questions:
- What is the exact value type? Distinguish the Boolean
false, string"false", number0, empty string,null, and a missing key. - What is the code fallback? This is the value compiled into the call site and used when no provider has a usable value.
- What is the legacy default? It may differ from the code fallback because a database or environment parser supplies another default.
- Which source wins? Write the precedence of hard-coded override, account override, environment variable, JSON file, database row, and default.
- Which condition wins? GrowthBook evaluates rules top to bottom and the first matching rule wins. Confirm whether the old system does the same.
- What is the environment? Use a deployment environment such as
dev,staging,test, orproduction, not an ambiguous build mode. - What attributes are used? Record exact names, types, casing, and missing-value behavior.
- What is the randomization unit? User, anonymous device, account, organization, session, or request are not interchangeable.
- How is a rollout bucket calculated? Record the seed, hash function, hash version, normalization, and range boundaries.
- How fresh is the value? Record cache duration, polling interval, or deployment requirement.
- What happens when the source fails? A timeout, malformed JSON, missing row, or database outage must have an observed behavior.
- Does evaluation have side effects? Some custom systems persist assignments or emit exposure events during reads.
Normalize truthiness before comparing
Do not preserve accidental JavaScript truthiness. Decode each legacy source once at startup and reject ambiguous values:Boolean("false") from becoming true. Make parser changes in a separate commit if they alter current behavior. A migration is easier to review when cleanup and provider replacement are not hidden in the same diff.
Prove cohort continuity instead of assuming it
A 10% legacy rollout and a 10% GrowthBook rollout usually include the same amount of traffic, not the same people. Different seeds, algorithms, attribute names, string normalization, or range boundaries change membership. Use one of these strategies:- Preserve a small explicit cohort: Import the existing account or user IDs into a GrowthBook Saved Group and target that group during migration. Keep ID lists concise because they increase the SDK payload.
- Pass a temporary legacy-cohort attribute: Continue calculating
legacy_invoice_preview=trueand target it in GrowthBook. Remove the attribute after the rollout completes. This preserves behavior but intentionally retains part of the legacy evaluator for a limited time. - Preserve persisted assignments: Export only the assignment data required by an approved migration design. Do not assume deterministic hashing can reproduce a stored assignment table.
- Accept re-bucketing: Do this only for a low-risk release, outside an active experiment, with written approval. Tell support and analytics when assignment changes.
- Wait for 0% or 100%: If the rollout can pause or finish safely, migrate after there is no partial cohort to preserve.
hashAttribute is necessary, but it is not proof of cohort parity. In GrowthBook, a percentage rule deterministically samples on the configured attribute. Missing the attribute causes the percentage rule to be skipped and evaluation falls through to the next rule or default. Test empty and missing identifiers explicitly.
Put one typed boundary in front of every read
Do this before GrowthBook controls a single production decision. The boundary lets you switch providers without editing business logic again. Install the SDK and test dependencies. Use the versions already accepted by your repository if they differ:Define feature types and code fallbacks
Createsrc/feature-flags/app-features.ts:
disable-payments has inverted semantics. Prefer positive names such as payments-enabled and document the fallback.
Remote payloads also need runtime validation. These validators preserve the legacy page-size choices and recommendation schema below; adapt them to your accepted semantic contract. Never coerce the string "false" into a Boolean or use a TypeScript cast as validation. Keep the validators when generating SDK types with the CLI.
During initial migration, maintain this interface manually. After all intended keys exist in GrowthBook, replace it with CLI-generated AppFeatures types and retain CODE_FALLBACKS as a checked application policy.
Define a request context
Createsrc/feature-flags/context.ts:
Define provider and decision receipts
Createsrc/feature-flags/provider.ts:
FeatureProvider creates a request scope. That matters on servers. A singleton GrowthBookClient reuses the loaded feature payload, while createScopedInstance keeps one request’s attributes and evaluation de-duplication separate from another request.
The 4 modes have deliberate meanings:
growthbook-preferred is transitional. Do not leave it forever. A missing GrowthBook key would silently keep legacy behavior and prevent the old system from being removed.
Snapshot the legacy system
Do not perform a database query for every feature read just because the adapter is new. Load the old configuration using its established freshness contract, validate it, and expose a synchronous snapshot. Createsrc/feature-flags/legacy-loader.ts:
loadRows to a parameterized, read-only query in your own database layer. Do not put a database client or SQL dialect into the flag interface.
If parsing fails, prefer failing application startup over silently changing a known production value. If the current application already tolerates malformed config, record that behavior and decide explicitly whether to preserve or fix it in a separate change.
Implement the legacy provider exactly once
Createsrc/feature-flags/legacy-provider.ts:
LegacyFlagProvider only. Deploy and verify that decisions are unchanged before adding GrowthBook. This isolates adapter mistakes from provider differences.
Create environment-scoped GrowthBook configuration
Use GrowthBook environments for where the code runs. Use projects for ownership and organization, not as a substitute for deployment environments. For the reference service:- Create or use the built-in
dev,test,staging, andproductionenvironments. - Create one SDK connection for each environment that the service uses.
- Put that environment’s client key in
GROWTHBOOK_CLIENT_KEYat deployment time. - Set
GROWTHBOOK_API_HOST=https://cdn.growthbook.iofor GrowthBook Cloud, or the documented endpoint for your self-hosted deployment. - Keep
DEPLOY_ENVseparate fromNODE_ENV. A staging server commonly runs a production Node build. - Do not place a production SDK client key in a local
.envexample.
AppFeatures. For the first shadow deployment:
- Match the accepted legacy default in every environment.
- Recreate deterministic targeting rules in the same top-to-bottom precedence.
- Do not create an experiment rule yet. Shadow evaluation should compare release behavior without emitting experiment exposures or contaminating analysis.
- Leave any partial rollout at 0% until the cohort-continuity strategy is resolved.
- Add a description that links to the migration issue, owner, safe fallback, and expected cleanup condition.
Map legacy semantics explicitly
Use a table like this during review:
Case matters. If the legacy system stores
country="us" and GrowthBook targets "US", either normalize the application attribute or configure the intended comparison. Do not “fix” casing only for some services.
Implement the GrowthBook provider
Createsrc/feature-flags/feature-refresh.ts. SDK 1.7.0 does not have the newer init({ pollingInterval }) option, so this example uses its supported refreshFeatures method. Start one timer per long-lived client, not per request.
src/feature-flags/feature-refresh.ts
refreshFeatures() resolves without a success receipt on ordinary network failures and retains the last payload; promise resolution is not proof of freshness. Monitor delivery and use a staging publish-to-evaluation check to prove propagation. Expect up to the polling interval plus delivery time while the endpoint is healthy, not an instantaneous kill switch. Define an operational staleness limit and escalation policy before production cutover.
Create src/feature-flags/growthbook-provider.ts:
init method reports failures in its return value instead of throwing for normal network and timeout failures. Until a usable payload loads, feature evaluation returns null; this adapter converts that state to the explicit call-site fallback and marks it unusable. A non-null value that fails its runtime contract is also unusable, with reason: "invalid-value". In growthbook-preferred mode that allows the legacy fallback; in growthbook-only mode it uses the code fallback.
fromPayload is for deterministic tests. It uses the same payload shape as the SDK connection endpoint without making a network request.
connect starts polling even if the first fetch fails, so later delivery can recover without restarting the service. fromPayload starts no timer. The Node.js SDK documentation also covers streaming; if you need faster propagation, configure the required EventSource support and test it before replacing this polling implementation.
Emit comparison evidence without leaking identities
Shadow mode can generate high-volume logs. Emit every mismatch, sample matches, and hash values before logging them. Do not send raw user IDs, account IDs, emails, feature JSON, or SDK payloads to ordinary logs. Createsrc/feature-flags/decision-telemetry.ts:
Serve one provider and compare the other
Createsrc/feature-flags/migrating-provider.ts:
Bootstrap once at process startup
Createsrc/feature-flags/bootstrap.ts:
growthbook-preferred can fall back. In growthbook-only, the snapshot is no longer needed for serving, so remove that dependency in a later cleanup commit after the observation window.
The SDK receipt may report source: "cache" and still be usable. Alert on success: false, fallback use, and a prolonged absence of fresh payloads according to your refresh design. Do not log the client key or CLI bearer token.
Change call sites without changing behavior
Before:FLAG_PROVIDER_MODE=legacy:
buildFeatureFlags inside the handler. SDK initialization and legacy snapshot loading belong at application startup. The request scope is cheap and isolates attributes; the control-plane payload remains shared.
Cut over one flag at a time
UseGROWTHBOOK_SERVE_KEYS as a comma-separated, startup-validated allowlist while the mode is growthbook-preferred. Selected keys prefer GrowthBook and temporarily fall back to legacy when the GrowthBook decision is unusable. Unselected keys continue serving legacy while comparison telemetry runs.
The complete sequence is:
Stage 1: Legacy through the adapter
Deploy:Stage 2: Shadow in development and staging
Deploy the correct non-production SDK client key:- No optional attributes.
- An internal user.
- A normal user.
- An allowlisted account.
- An excluded account.
- A context matching 2 ordered rules.
- Missing and empty hash attributes.
- Every environment in which the flag is delivered.
Stage 3: Shadow in production
Production shadow mode still serves legacy decisions. Roll it out like ordinary observability code and watch its resource cost. Do not log raw values or identities. An expected matching receipt is sampled:Stage 4: Serve one low-risk key
After the key’s mismatch queue is empty or explained, deploy:servedBy: "growthbook" for search-page-size and servedBy: "legacy" for every unselected key. Change the GrowthBook value in a non-production environment, publish it, and verify the service receives the change within the documented refresh window.
Then select the first production flag. Prefer a reversible, low-risk release flag with no partial cohort and no experiment attached. Observe:
- Error rate and latency on both behavior paths.
- GrowthBook startup and refresh success.
- Count of
servedBy: "legacy"fallbacks for a selected key. - Decision mismatch rate.
- Missing identity and missing
account_idrates. - Support or business symptoms specific to the feature.
growthbook-preferred serves no keys from GrowthBook. That is a safe configuration, not an instruction to migrate everything.
Stage 5: Remove the temporary legacy fallback
When every key is selected and stable, deploy:CODE_FALLBACKS, not the legacy system. Run a controlled failure test in staging by using an invalid client key or blocking the SDK endpoint. Confirm startup reports failure and each flag takes its documented code fallback.
Do not simulate this by disabling a production feature without understanding the result. A disabled feature is removed from the payload and therefore exercises the fallback path for every user.
Explain mismatches systematically
Do not average all flags into one mismatch percentage. A 0.1% mismatch in a payment kill switch can matter more than a 20% documented re-bucketing in a cosmetic rollout. Classify every mismatch into one of these causes:- Missing feature: The GrowthBook SDK payload does not contain the key. Check feature state, environment, project delivery, SDK connection scope, and spelling.
- Wrong default: The feature exists, but its default differs from the legacy value.
- Rule order: A context matches multiple rules and the first match differs.
- Attribute name:
accountIdandaccount_idare different keys. - Attribute type: The old system compares a number while the new attribute is a string.
- Normalization: Casing, whitespace, Unicode, date formatting, or semantic version handling differs.
- Missing identity: The percentage rule has no value for its hash attribute and falls through.
- Cohort re-bucketing: Both providers include the intended percentage but assign different subjects.
- Staleness: One provider has a newer value than the other because update and cache timing differ.
- JSON representation: Object key order differs but values are equivalent, or a genuinely different nested value exists.
- Legacy side effect: A read created or changed an assignment.
- Concurrent mutation: Someone changed one control plane without mirroring the other.
Test the provider contract
Tests should use explicit feature payloads. Do not make unit tests depend on the current state of a shared GrowthBook organization.Test GrowthBook targeting and fallbacks
Createtests/feature-flags/growthbook-provider.test.ts:
Test serving mode and the per-key allowlist
Createtests/feature-flags/migrating-provider.test.ts:
Add parity fixtures for real production shapes
The previous tests prove adapter mechanics. A parity contract proves your rules. Build fixtures from synthetic examples that cover actual branches; do not copy production PII into the repository. Createtests/feature-flags/parity-contract.test.ts:
Generate strict feature types in CI
Once the GrowthBook project contains the intended keys and value types, generateAppFeatures from GrowthBook instead of maintaining it twice. The current GrowthBook CLI is installed from the growthbook npm package and its generate-types command writes a TypeScript AppFeatures definition.
Install and authenticate interactively on a workstation:
/api suffix. In CI, use a narrowly scoped Secret Key or Personal Access Token in GBCLI_BEARER_AUTH and disable interactive prompts. Never use the public SDK client key as REST API authentication.
After generation, make app-features.ts re-export the generated interface and keep fallbacks checked against it:
satisfies constraint makes CI fail when GrowthBook adds a key without an application fallback, removes a key still used by the application, or changes a value type incompatibly.
Create .github/workflows/feature-flag-contract.yml:
Add operational diagnostics
Shadow telemetry answers “do the providers agree?” GrowthBook Feature Evaluation Diagnostics can answer “which values and rules are the live SDKs evaluating?” when you send evaluation events to your warehouse and configure a Feature Usage Query. The JavaScript/Node SDK supportsonFeatureUsage. The callback includes the feature key, evaluation result, and, for the multi-user client, the user context. It is de-duplicated for repeated evaluation of the same value within a scoped context. That makes it useful for recent decision diagnostics, but it is not a request-count metric.
Add a warehouse-bound callback when your privacy and event-volume policies permit it:
unit_id according to your data policy. Do not send email, raw secure attributes, roles, or the entire user context by default. Partition a high-volume evaluation table by timestamp and, where supported, cluster by feature key so recent diagnostic queries do not scan unbounded history.
Track these service-level metrics independently of warehouse diagnostics:
- SDK initialization successes and failures by deployment environment.
- Payload source: network, cache, timeout, or error.
- Refresh age and refresh failures.
- GrowthBook code-fallback count by key.
- Transitional legacy-fallback count by selected key.
- Shadow comparison count and mismatch count by key and reason.
- Missing randomization-attribute count.
- Decision latency around the provider call.
Handle failures deliberately
The code fallback must be safe before the incident occurs. During an outage, nobody should debate whether
false disables the new checkout or disables checkout entirely.
Govern the system you actually have
GrowthBook supports drafts and published revisions for feature changes. Optional approval flows and some advanced governance features depend on plan and organization configuration. Build a minimum operational policy that works on your current plan, then add product-enforced controls where available.Minimum policy on any plan
- Give every flag an owner and a plain-language purpose.
- Record the safe fallback and incident action.
- Separate development, test, staging, and production SDK connections.
- Require a change record or pull request for production rule changes, even when the product does not enforce an approval.
- Use a second-person review for high-risk operational and release changes.
- Put an expected cleanup condition and date on temporary flags.
- Test the proposed attributes in Simulation before publishing.
- Verify the published revision through application telemetry after change.
- Keep permissions least-privileged.
Product-enforced controls when available
Approval flows can require a different user to review a draft before publishing. Publishing creates immutable revisions, and a prior revision can be selected and reverted through a new change. Configure approval requirements for production environments according to your plan and risk model. Do not claim that every GrowthBook plan enforces the same workflow. If approvals, custom roles, custom environments, schedules, or another control is required, confirm its current availability before making it a migration dependency.Naming and ownership
Feature keys are durable API names. Use lowercase kebab case, avoid team names that may change, and encode the behavior rather than the implementation ticket:Add code references and stale review
Code References scans a checked-out repository and sends the locations of feature keys to GrowthBook. Run the scanner in CI on the branch or branches your team uses for cleanup decisions. It lets reviewers see where a flag remains in code and improves the evidence available during stale review. The currentgb-find-code-refs tool can run directly or through its Docker image. Its exact invocation and credentials depend on the integration you configure, so use the official repository and pin a reviewed release or image digest rather than copying an unpinned latest command into production CI.
Code references are evidence, not deletion authorization. Dynamic keys, aliases, generated clients, another repository, mobile releases, scheduled jobs, and old deployed versions may not appear in one scan.
GrowthBook marks a feature stale after it has not been updated for 2 weeks and either has no active environment or has one-sided rules that send 100% of traffic to one variation. Use that as a review queue. It does not prove the flag can be deleted automatically.
For each stale candidate:
- Confirm the production value and rule state.
- Check code references on the protected default branch.
- Search all repositories and clients that consume the SDK connection.
- Check support, mobile, worker, and backward-compatibility requirements.
- Decide which behavior becomes permanent.
- Remove the losing branch from code.
- Deploy and observe the cleanup.
- Remove the flag read.
- Re-run code references.
- Archive or delete the GrowthBook feature according to team policy.
Remove the legacy system in the correct order
The final cleanup is another migration, not housekeeping to squeeze into the cutover deployment.1. Stop legacy mutations
Disable the old admin endpoint, UI, scheduled updater, and manual database write procedure. Return a clear error that points operators to GrowthBook. Keep reads intact temporarily. Verify no writes for the observation window:2. Remove legacy fallback reads
Rungrowthbook-only long enough to exercise an SDK refresh and normal production traffic. Remove growthbook-preferred, GROWTHBOOK_SERVE_KEYS, and the legacy provider dependency. Simplify the runtime interface to GrowthBook plus code fallbacks.
Search for old keys again:
3. Remove data after backup and retention review
Export the legacy schema and row counts to the approved backup location. Do not place raw overrides in a pull request artifact. Verify restore access before dropping a table. Prefer this sequence:4. Delete migration-only telemetry
Remove pairwise comparison logs and the telemetry salt after the migration window. Retain normal GrowthBook diagnostics and fallback alerts. High-volume shadow instrumentation is not the permanent operating model.5. Close the migration record
Record:- Final application commit and deployment revision.
- GrowthBook feature keys and owning project.
- Date legacy writes stopped.
- Date legacy reads stopped.
- Backup and retention location.
- Any accepted cohort discontinuity.
- Rollback image or release expiration.
- Remaining temporary flags and their cleanup dates.
Optional OpenFeature branch
OpenFeature is useful when a team has already standardized on its vendor-neutral evaluation API across multiple services, or portability is a stated architecture requirement. Do not add it merely to make this migration look more abstract. At the commit verified for this guide, GrowthBook documents OpenFeature providers for Python, Go, .NET, and Java. The native GrowthBook SDKs expose capabilities outside the OpenFeature interface, and the documented provider list does not include an official JavaScript/TypeScript provider. Therefore, the reference Node implementation uses the native SDK behind an application-owned interface. Use an official GrowthBook OpenFeature provider in a supported language when all of these are true:- The service already uses OpenFeature or has an approved cross-language standardization plan.
- The provider supports the GrowthBook capabilities you need.
- Your evaluation context maps the stable
targetingKeyand additional attributes correctly. - Provider initialization, shutdown, cache, and failure behavior are covered by contract tests.
- The team accepts that native-only capabilities may require an escape hatch.
FeatureProvider in this guide already limits vendor-specific code to one file without claiming standards compliance.
DIY versus GrowthBook: make the decision honestly
Keep or improve DIY when all of these remain true:- The switch is static or deploy-time by design.
- One application and one team own it.
- No user, account, or percentage targeting is needed.
- A deployment rollback meets the operational requirement.
- No experiment exposure or outcome analysis is planned.
- The switch will not multiply into a shared control plane.
- A typed configuration parser and tests solve the actual problem.
Definition of done
Inventory and classification
- Repository, deployment, configuration, and database searches are complete.
- Every confirmed switch has a manifest row and owner.
- Every candidate is classified as release, experiment, operational, permission/entitlement, permanent configuration, build-time configuration, or dead code.
- Authorization, billing entitlements, secrets, executable content, and safety invariants remain outside ordinary flags.
- Dead flags are deleted rather than migrated.
Semantic parity
- Value type, code fallback, legacy default, precedence, rule order, environment, attributes, randomization unit, hashing, freshness, and failure behavior are documented per key.
- Ambiguous string and Boolean coercion is removed or explicitly preserved.
- Partial rollouts have a cohort-preservation or accepted re-bucketing decision.
- Missing identity and missing account attributes are tested.
- Synthetic parity fixtures cover every meaningful branch.
Runtime migration
- Every call site uses the typed provider boundary.
- Legacy-only adapter deployment preserved behavior.
- Correct environment-scoped SDK client keys are installed.
- Shadow evaluation has no side effects.
- Every unexplained production mismatch is resolved.
- GrowthBook serves only allowlisted keys during incremental cutover.
- Selected-key legacy fallback count is zero in the acceptance window.
- GrowthBook-only failure tests produce documented code fallbacks.
- Application shutdown closes the SDK provider cleanly.
Tests and operations
- Unit, multi-user isolation, targeting, mode, parity, and failure tests pass.
- Generated feature types match the checked-in file.
- CI uses a pinned CLI and least-privileged secret.
- Startup, refresh, fallback, mismatch, missing-attribute, and latency signals exist.
- Production change review matches the current plan’s available controls.
- Operators can revert a feature revision and can redeploy
FLAG_PROVIDER_MODE=legacyduring the rollback window.
Cleanup
- Legacy mutation paths are disabled and observed unused.
- GrowthBook-only mode runs before legacy reads are removed.
- Legacy credentials are revoked.
- Legacy data is backed up according to retention policy before deletion.
- Code References run on protected branches.
- Stale flags are reviewed with code and deployment evidence, never deleted automatically.
- Migration-only telemetry and secrets are removed.
- Every remaining temporary flag has a cleanup owner and condition.
Source map and freshness
This guide was verified on 2026-08-12 against GrowthBook repository commite44a15af063860c7118f52508746356d55e5a91d and these source areas:
The highest-drift details are package versions, CLI commands, plan availability, environment limits, provider coverage, and stale-detection criteria. Recheck them before publishing this guide or using it as an automated migration instruction.
The application-specific details drift faster than the product docs. Re-run the inventory, regenerate types, rebuild parity fixtures, and capture a new source commit whenever the legacy implementation or target GrowthBook configuration changes.

