Featurevisor

Migration guides

Migrating from v2 to v3

Detailed guide for migrating existing Featurevisor projects (using Featurevisor CLI) and applications (using Featurevisor SDKs) to latest v3.0.


Migrate in two steps

You do not have to migrate everything at once. The recommended path is linear, with the project first and the applications after:

  1. Upgrade your Featurevisor project: update the CLI, adjust your configuration and definitions, build the datafiles, and deploy them.
  2. Upgrade your applications: once the new datafiles are published, update each application's SDK at your own pace.

The datafile shape has not changed

The generated datafile keeps the same schema version (2) as v2, so its shape is unchanged. No new functionalities have been introduced either that would break existing applications.

This means the datafile structure remains readable by applications using existing v2 SDKs.

Behaviour changes

There is one behavioural exception to review before publishing v3 datafiles: segments.not now uses the same implicit AND semantics as conditions.

Projects without segments.not expressions that have multiple children can finish step 1 and publish v3 datafiles while applications continue using v2 SDKs. There is no need to coordinate a single big release across all applications.

Once the datafiles are out, you can come back and upgrade the SDK in each application one at a time.


Project configuration

Environments are off by default Breaking

In v2, every project had staging and production environments by default. In v3, projects have no environments unless you declare them.

If your project uses environments, declare them explicitly in your configuration:

Before
featurevisor.config.js
module.exports = {
// staging and production
// were assumed by default
tags: ['all'],
}
After
featurevisor.config.js
module.exports = {
environments: [
'staging',
'production',
],
tags: ['all'],
}

If your project does not need environments, you can leave environments out, and author your rules, force, and expose directly without an environment key.

The old environments: false value is no longer needed. Remove the property instead.

Learn more in Environments page.

Split environment files removed Breaking

The splitByEnvironment option and the top-level environments/ definition directory have been removed. Environment-specific rules, force, and expose now belong in their feature file, keyed by environment.

Before
features/
checkout.yml
environments/
staging/
checkout.yml
production/
checkout.yml
After
features/checkout.yml
description: Checkout
bucketBy: userId
rules:
staging:
- key: everyone
segments: '*'
percentage: 100
production:
- key: everyone
segments: '*'
percentage: 0

Remove splitByEnvironment from featurevisor.config.js after moving the files. If those directories represented independently promoted copies rather than environment-specific rollout fields, consider migrating them to Sets instead.

Scopes replaced by targets Breaking

The scopes configuration key has been removed. Datafiles are now built from targets, which live as files in the targets directory.

Before
featurevisor.config.js
module.exports = {
scopes: [
{
name: 'browsers',
tag: 'web',
context: { platform: 'web' },
},
],
}
After
targets/browsers.yml
description: Web browsers
tag: web
context:
platform: web

A target's tag, tags, and context work exactly like a scope did, including the or and and selectors. If scopes is still present in your configuration, Featurevisor ignores it. Migrate each old scope to a target file, because obsolete scopes entries no longer build any datafiles.

Learn more in Targets and Scopes pages.

Namespace separator Breaking

Namespaced feature and segment keys now use a dot (.) as their separator by default, instead of a slash (/).

This means features/checkout/feature1.yml is now referred to as checkout.feature1 instead of checkout/feature1, both in your application and in your test specs.

To keep the old slash-separated keys, set namespaceCharacter in your configuration:

featurevisor.config.js
module.exports = {
namespaceCharacter: '/',
}

Learn more in Namespaces page.

Sets New

You can now split a project into independent trees called sets, each owning its own attributes, segments, features, targets, and tests.

featurevisor.config.js
module.exports = {
sets: true,
tags: ['all'],
}

This is useful for modeling release lanes like dev, staging, and production, or distinct surfaces like storefront and admin, from a single repository.

Learn more in Sets page.

Promotions New

In a project with sets, you can copy definitions from one set to another using promotions. You can optionally constrain which promotions are allowed with promotionFlows:

featurevisor.config.js
module.exports = {
sets: true,
tags: ['all'],
promotionFlows: [
{ from: 'dev', to: 'staging' },
{ from: 'staging', to: 'production' },
],
}

Learn more in Promotions page.


Defining features

Tags are now optional New

The tags property on a feature is now optional. A feature without tags is still included in targets that do not narrow down by tag.

features/myFeature.yml
description: My feature
bucketBy: userId
rules:
- key: everyone
segments: '*'
percentage: 100

Learn more in Features section.

not uses implicit AND semantics Breaking

For both conditions and rule segments, direct children of not are treated as an implicit AND. In v3, segments.not is aligned with the condition behavior.

Use a nested or when the intention is "none of these match":

features/myFeature.yml
rules:
- key: neither-premium-nor-internal
segments:
not:
- or:
- premium
- internal
percentage: 100

Use a nested and when the intention is "not all of these match":

features/myFeature.yml
rules:
- key: not-both-premium-and-internal
segments:
not:
- and:
- premium
- internal
percentage: 100

Keep v2 and v3 SDKs compatible

During a gradual SDK rollout, avoid placing multiple segment names directly under not. Put one explicit or or and group under it instead.

Both SDK versions then see a single direct child and negate the same grouped result:

  • not: [{ or: [...] }] means none of the listed segments match.
  • not: [{ and: [...] }] means not all of the listed segments match.

For an existing v2 rule with several direct children under not, wrap those children in or before publishing a datafile that will be consumed by both v2 and v3 applications. This preserves the v2 "none match" behavior throughout the transition. After every application uses a v3 SDK, the explicit grouping can remain because it is clearer and behaves identically.

Empty and, or, and not arrays are now rejected during linting. Update any generated or placeholder expressions before upgrading.

Promotable definitions New

In a project with sets, you can protect an existing destination definition from later promotion updates by setting promotable: false. A missing destination definition is still created. On rules, a source rule with this field is omitted and an existing destination rule is preserved. This is supported on features, rules, segments, attributes, groups, schemas, targets, and test specs.

features/myFeature.yml
description: My feature
promotable: false
# ...

Learn more in Promotions page.


Testing features

Targeting datafiles in assertions Breaking

The scope and tag properties in test assertions have been replaced by a single target property.

Before
tests/features/myFeature.spec.yml
feature: myFeature
assertions:
- environment: production
scope: browsers
at: 50
context: {}
expectedToBeEnabled: true
After
tests/features/myFeature.spec.yml
feature: myFeature
assertions:
- environment: production
target: browsers
at: 50
context: {}
expectedToBeEnabled: true

The --with-scopes and --with-tags CLI flags are no longer needed. The test runner builds target datafiles in memory automatically. Old scripts may still pass these flags, but they are ignored.

Learn more in Testing page.

Testing sets New

In a project with sets, tests run for every set by default. You can run tests for a single set with --set:

Command
$ npx featurevisor test --set=storefront

CLI usage

Upgrade to latest CLI New

In your Featurevisor project repository:

Command
$ npm install --save @featurevisor/cli@3

Building datafiles from targets Breaking

Datafiles are no longer built per tag automatically. You now need at least one target for the build to produce anything.

The smallest possible target includes all of your non-archived features:

targets/all.yml
description: All features

Then build as usual:

Command
$ npx featurevisor build

Datafile naming Breaking

Because datafiles are built from targets now, the tag- prefix in datafile names is gone. A datafile is named after the target that produced it.

Before
$ tree datafiles
.
├── production
│ └── featurevisor-tag-web.json
└── staging
└── featurevisor-tag-web.json
After
$ tree datafiles
.
├── production
│ └── featurevisor-web.json
└── staging
└── featurevisor-web.json

Define a target named web (with tag: web) to produce featurevisor-web.json.

To maintain backwards compatibility, you can create new targets named tag-web and tag-mobile to produce featurevisor-tag-web.json and featurevisor-tag-mobile.json respectively.

Learn more in Building datafiles page.

Schema version selection removed Breaking

Featurevisor v3 no longer builds v1-compatible datafiles. Generated datafiles always use schema version 2, and still include the normal schemaVersion: "2" field.

The old --schema-version and --schemaVersion flags are no longer part of active CLI usage. If they are still passed by old scripts, they are ignored and datafiles are still generated as schema version 2.

If you still have an application consuming v1 datafiles, keep building those datafiles with an older Featurevisor v2 CLI until the application can move to a v2-datafile-compatible SDK.

Catalog instead of site Breaking

The old site package and command have been replaced by the new standalone @featurevisor/catalog package, exposed through the catalog command.

Before
$ npx featurevisor site export
$ npx featurevisor site serve
After
$ npx featurevisor catalog export
$ npx featurevisor catalog serve

The default export directory changes from out/ to catalog/. Replace siteExportDirectoryPath with catalogDirectoryPath if your project customizes it, and update deployment workflows to publish the new directory. Running npx featurevisor catalog without a subcommand now starts a watched local development session.

Learn more in Catalog page.

Promote command New

In a project with sets, you can preview and apply promotions between sets:

Command
$ npx featurevisor promote --from=dev --to=staging
$ npx featurevisor promote --from=dev --to=staging --apply
$ npx featurevisor promote --from=dev --to=staging --target=checkout --apply

Promotion filters such as --target, --tag, --includeFeatures, and --excludeFeatures limit the selected features while automatically including the segments they reference.

Learn more in Promotions page.

Per-feature generated modules removed Breaking

The TypeScript code generator no longer creates individual *Feature.ts namespaces. The --no-individual-features option has also been removed because the function API is now the only generated API.

Run the v3 code generator with the same project-level command, without --no-individual-features:

Command
$ npx featurevisor generate-code \
--language typescript \
--out-dir ./src \
--react

Delete the old generated output before running the v3 generator so stale *Feature.ts files do not remain in your project. Replace namespace imports with the typed shared functions:

Before
import { CheckoutFeature } from '@yourorg/features'
const enabled = CheckoutFeature.isEnabled(context)
const variation = CheckoutFeature.getVariation(context)
const color = CheckoutFeature.getColor(context)
After
import {
isEnabled,
getVariation,
getVariable,
} from '@yourorg/features'
const enabled = isEnabled('checkout', context)
const variation = getVariation('checkout', context)
const color = getVariable('checkout', 'color', context)

Feature keys, variable keys, variation values, variable values, and context remain type safe through the shared generated functions. Learn more in Code Generation.

Code generation also accepts repeatable --tag and --target options in v3. All supplied selectors form a union, allowing one generated package to cover several target datafiles.

In a project with sets enabled, generate-code requires --set=<set> because one generated package represents one set's definitions.

Target selection is also available on build, test, evaluate, benchmark, assess-distribution, list --features, and info. The option is repeatable where several targets can be processed. Each runtime command evaluates every selected target independently. A normal build writes every selected target datafile, while build --json and build --print accept only one target because they emit one datafile.

Working with a single set New

Most commands accept a --set flag to scope them to one set:

Command
$ npx featurevisor build --set=storefront
$ npx featurevisor test --set=storefront

JavaScript SDK usage

Upgrade to latest SDK New

In your application repository:

Command
$ npm install --save @featurevisor/sdk@3

Creating an instance Breaking

createInstance has been renamed to createFeaturevisor.

Before
your-app/index.js
import { createInstance } from '@featurevisor/sdk'
const f = createInstance({
datafile: datafileContent,
})
After
your-app/index.js
import { createFeaturevisor } from '@featurevisor/sdk'
const f = createFeaturevisor({
datafile: datafileContent,
})

Instance type renamed Breaking

The FeaturevisorInstance type has been renamed to Featurevisor.

Before
your-app/index.ts
import type { FeaturevisorInstance } from '@featurevisor/sdk'
let f: FeaturevisorInstance
After
your-app/index.ts
import type { Featurevisor } from '@featurevisor/sdk'
let f: Featurevisor

Options type renamed Breaking

The constructor options type has been renamed from InstanceOptions to FeaturevisorOptions.

Before
your-app/featurevisor.ts
import type { InstanceOptions } from '@featurevisor/sdk'
const options: InstanceOptions = {
datafile: datafileContent,
}
After
your-app/featurevisor.ts
import type { FeaturevisorOptions } from '@featurevisor/sdk'
const options: FeaturevisorOptions = {
datafile: datafileContent,
}

Learn more in JavaScript SDK page.

setDatafile merges by default Soft breaking

In v2, calling setDatafile replaced the instance's datafile. In v3, it merges with the existing datafile by default.

Incoming features and segments override matching keys, while existing ones that are missing from the incoming datafile are kept. To get the old replacing behavior, pass true as the second argument.

Before
your-app/index.js
// always replaced
f.setDatafile(datafileContent)
After
your-app/index.js
// merges by default
f.setDatafile(datafileContent)
// pass true to replace entirely
f.setDatafile(datafileContent, true)

Merging makes it possible to load smaller datafiles on demand. Learn more in Loading datafiles on demand.

Sticky values belong to instances Breaking

The per-evaluation sticky override has been removed. Set sticky values on the main instance, or give a child instance its own sticky values when spawning it.

Before
your-app/index.js
const enabled = f.isEnabled('checkout', context, {
sticky: stickyFeatures,
})
After
your-app/index.js
f.setSticky(stickyFeatures)
const enabled = f.isEnabled('checkout', context)

For isolated state, use f.spawn(context, { sticky: stickyFeatures }) and evaluate through the returned child instance.

Diagnostics instead of logger Breaking

The logger option and the createLogger function have been removed. The SDK now reports diagnostics, which you control with logLevel and an optional onDiagnostic handler.

Before
your-app/index.js
import {
createInstance,
createLogger,
} from '@featurevisor/sdk'
const f = createInstance({
logger: createLogger({
level: 'debug',
}),
})
After
your-app/index.js
import { createFeaturevisor } from '@featurevisor/sdk'
const f = createFeaturevisor({
logLevel: 'debug',
})

You can pass your own handler if you do not want diagnostics printed to the console:

your-app/index.js
const f = createFeaturevisor({
logLevel: 'info',
onDiagnostic: function (diagnostic) {
// send to your observability system
},
})

The setLogLevel method still works for changing the level at runtime.

Learn more in Diagnostics section.

Modules instead of hooks Breaking

The hooks API has been replaced by the modules API. The Hook type is now FeaturevisorModule, hooks is now modules, and addHook is now addModule.

Your existing before, after, bucketKey, and bucketValue callbacks carry over unchanged. Modules also add an optional setup lifecycle and a close callback.

Before
your-app/index.js
import { createInstance } from '@featurevisor/sdk'
const f = createInstance({
hooks: [myCustomHook],
})
const removeHook = f.addHook(myCustomHook)
After
your-app/index.js
import { createFeaturevisor } from '@featurevisor/sdk'
const f = createFeaturevisor({
modules: [myCustomModule],
})
const removeModule = f.addModule(myCustomModule)
await removeModule?.()
await f.removeModule('my-custom-module')

The old interceptContext, configureBucketKey, and configureBucketValue options, which were already replaced by hooks in v2, are now expressed as modules. Module cleanup can be asynchronous, so the function returned by addModule and removeModule should be awaited.

Learn more in Modules section.

Public SDK surface reduced Breaking

The package root now focuses on createFeaturevisor, the Featurevisor type, child instances, evaluations, events, diagnostics, modules, and Featurevisor data types.

DatafileReader, logger and emitter implementations, evaluator dependency objects, and other internal helpers are no longer public APIs. If application code used DatafileReader to inspect the active datafile, use the corresponding instance methods instead:

Removed usageInstance replacement
reader.getRevision()f.getRevision()
reader.getSchemaVersion()f.getSchemaVersion()
reader.getSegment(key)f.getSegment(key)
reader.getFeature(key)f.getFeature(key)
reader.getFeatureKeys()f.getFeatureKeys()
reader.getVariableKeys(featureKey)f.getVariableKeys(featureKey)
reader.hasVariations(featureKey)f.hasVariations(featureKey)

Remove direct imports of undocumented logger, emitter, reader, and evaluator helpers. Use diagnostics, events, and the main instance API instead.

The v1-only DatafileContentV1, FeatureV1, VariationV1, and VariableV1 types have also been removed. Use the normal v2 datafile types. VariableValue no longer includes undefined; missing evaluated values use null.


Other SDK usage

The v3 SDKs use the same Featurevisor focused factory naming as the JavaScript SDK. The old instance factory names are removed as a clean breaking change.

SDKv2 factoryv3 factory
GoNewFeaturevisorCreateFeaturevisor
SwiftcreateInstancecreateFeaturevisor
JavaFeaturevisor.createInstanceFeaturevisor.createFeaturevisor
RubyFeaturevisor.create_instanceFeaturevisor.create_featurevisor
Pythoncreate_instancecreate_featurevisor
PHPFeaturevisor::createInstanceFeaturevisor::createFeaturevisor

Go now uses FeaturevisorOptions instead of Options. Java now uses Featurevisor.FeaturevisorOptions instead of Featurevisor.Options. Swift and Python expose the main instance as Featurevisor instead of FeaturevisorInstance.

Update imports, type annotations, factory calls, and options type names together when upgrading an application. See the relevant SDK guide for language specific examples.

Custom logger injection has also been removed from all v3 SDKs. Configure diagnostic verbosity with logLevel, update it with setLogLevel or the idiomatic equivalent, and send structured diagnostics to your observability system with onDiagnostic. Remove uses of createLogger, create_logger, NewLogger, logger handler options, and logger fields in SDK options.

The same runtime changes apply across the v3 SDKs:

  • hooks are replaced by modules with setup, before, bucketKey, bucketValue, after, and close lifecycle callbacks;
  • setDatafile merges features and segments by default, with an explicit replace argument for the old behavior;
  • sticky values are configured on the main or child instance instead of individual evaluations;
  • diagnostics replace custom logger injection;
  • datafiles use schema version 2 only.

The exact method names and callback types follow each language's conventions. Review the relevant SDK guide before upgrading and update code and tests together.

React and Vue SDK usage

The React and Vue packages now expect the renamed createFeaturevisor function and Featurevisor type. Update any provider, application setup, or utility code that imports the old factory or type.

The React provider still receives an already-created instance:

your-app/index.jsx
import { createFeaturevisor } from '@featurevisor/sdk'
import { FeaturevisorProvider } from '@featurevisor/react'
const f = createFeaturevisor({ datafile: datafileContent })
root.render(
<FeaturevisorProvider instance={f}>
<App />
</FeaturevisorProvider>,
)

If an older Vue setup still passes datafileUrl, fetch the datafile before creating the instance instead:

Before
your-app/index.js
import { createInstance } from '@featurevisor/sdk'
const f = createInstance({
datafileUrl: DATAFILE_URL,
})
After
your-app/index.js
import { createFeaturevisor } from '@featurevisor/sdk'
const datafile = await fetch(DATAFILE_URL)
.then(response => response.json())
const f = createFeaturevisor({ datafile })

The Vue package no longer ships useStatus or activateFeature. To track exposure, register a module that reacts to evaluations in its after callback.

Learn more in React SDK and Vue.js SDK pages.


Migration checklist

Use this order for each existing project:

  1. Upgrade @featurevisor/cli and declare environments explicitly when needed.
  2. Move split environment files back into feature definitions, or model independent trees as sets.
  3. Convert every scope into a target and add at least one target for building datafiles.
  4. Decide whether to keep slash-separated namespace keys or migrate application and test references to dots.
  5. Review multi-child segments.not expressions and remove empty logical arrays.
  6. Replace assertion scope and tag fields with target, then run the complete test suite.
  7. Build and inspect every target and environment datafile before publishing it.
  8. Update Catalog, promotion, code-generation, and CI commands and output paths used by automation.
  9. Upgrade each application SDK, update factory and type names, and replace logger and hook integrations.
  10. Review setDatafile, sticky ownership, module cleanup, and removed helper imports in application code.
  11. Run application tests against the same v3-generated datafiles that will be deployed.

The project and application upgrades can remain separate deployments as described at the start of this guide.

Previous
Deprecating features
Next
v2