Multi-set workflows
Promotions
Promotions copy definitions from one set to another, so you can move work along release lanes like dev, staging, and production.
Pre-requisites#
Promotions only apply to projects that use sets. Each lane is modelled as its own set, for example dev, staging, and production, each owning its own attributes, segments, features, targets, and tests.
module.exports = { sets: true, tags: ['all'],}The source and destination must be different existing sets. Featurevisor lints both sets before calculating a promotion and stops without writing files if either set has errors.
Previewing a promotion#
The promote command previews what would be copied from one set to another. Nothing is written to disk until you ask for it:
$ npx featurevisor promote --from=dev --to=stagingThis shows the definitions that would be created or updated in the destination set. When you promote a feature, Featurevisor also brings along its dependencies, like the segments and attributes it references.
To include unchanged entries in the preview:
$ npx featurevisor promote --from=dev --to=staging --showUnchangedApplying a promotion#
Pass --apply to write the destination files:
$ npx featurevisor promote --from=dev --to=staging --applyApplied definitions are ordinary project files. Review the Git diff, then lint and test the destination set before committing them:
$ npx featurevisor lint --set=staging$ npx featurevisor test --set=stagingFiltering features#
Filters choose the initial features for a promotion. Featurevisor then includes their complete dependency closure, including required features, exclusion groups, segments, nested segments, attributes, schemas, and relevant tests.
To promote everything needed to build one target's datafile, pass the source target key:
$ npx featurevisor promote --from=dev --to=staging --target=webThis uses the target's tag or tags, includeFeatures, and excludeFeatures selectors with the same semantics as a datafile build. It also promotes the target definition itself. Target context can simplify the eventual datafile, but promotion keeps the full definitions needed by the selected features.
To select features carrying a tag:
$ npx featurevisor promote --from=dev --to=staging --tag=webYou can also select features by key using glob patterns:
$ npx featurevisor promote --from=dev --to=staging --includeFeatures="checkout*"To start with all features and exclude matching keys:
$ npx featurevisor promote --from=dev --to=staging --excludeFeatures="experimental*"Filters can be combined. Positive selectors such as --target, --tag, and --includeFeatures use AND semantics. --excludeFeatures is applied last and takes precedence. Dependency closure may add required features or exclusion group members even when they do not match the initial filters.
--target, --tag, --includeFeatures, and --excludeFeatures are repeatable when more than one selector is needed.
If the final initial selection contains no features, the command fails so the mistake is visible early. Pass --allowEmpty if an empty result is acceptable. An unknown --target or --tag always fails and lists the available source values.
Conflicts#
When a definition already exists in the destination set with different values, that is a conflict. The --conflicts option controls how conflicts are resolved:
source: the source set wins and overwrites the destination (default)destination: the destination keeps its existing valuesfail: stop with an error instead of overwriting
$ npx featurevisor promote --from=dev --to=staging --conflicts=failProtecting definitions during promotion#
By default, definitions, feature rules, and test assertions are promotable. Use promotable: false when part of a destination set must remain under that set's control, such as a production rollout percentage, a production-only assertion, or a target with lane-specific selectors.
Supported locations#
promotable is supported in these locations:
| Location | Placement | Promotion behaviour |
|---|---|---|
| Attribute | Top level of the attribute file | Protects the whole destination attribute |
| Segment | Top level of the segment file | Protects the whole destination segment |
| Feature | Top level of the feature file | Protects the whole destination feature |
| Feature rule | On an individual entry under rules | Omits a source rule or preserves its matching destination rule |
| Group | Top level of the group file | Protects the whole destination group |
| Reusable schema | Top level of the schema file | Protects the whole destination schema |
| Target | Top level of the target file | Protects the whole destination target |
| Feature or segment test spec | Top level of the test spec file | Protects the whole destination test spec |
| Feature or segment test assertion | On an individual keyed entry under assertions | Omits a source assertion or preserves its matching destination assertion |
It is not supported as promotion protection on individual matrix cases, child assertions, group slots, segment conditions, force entries, variable overrides, or nested schema properties. Linting rejects it in most unsupported locations. Protect the parent assertion when all of its matrix cases or child assertions must stay together.
Whole definitions#
Set promotable: false at the top level of a definition:
description: Checkout flowpromotable: falsetags: - allrules: - key: everyone segments: '*' percentage: 100The result depends on whether the matching definition already exists in the destination:
| Source | Destination | Result |
|---|---|---|
promotable: false | Exists | Keep the destination unchanged |
| Promotable | promotable: false | Keep the destination unchanged |
promotable: false | Missing | Create it and retain promotable: false |
The protection takes precedence over --conflicts. The matching definition is determined by its entity key, which comes from its file path.
This top level behaviour applies to attributes, segments, features, groups, reusable schemas, targets, and feature or segment test specs.
For example, a lane-specific test spec can protect all of its assertions together:
feature: checkoutFlowpromotable: falseassertions: - description: Keep the production rollout disabled at: 50 context: userId: production-user expectedToBeEnabled: falseIndividual feature rules#
Rules have item-level protection because each rule has a stable key. A source rule with promotable: false is omitted from the promoted feature. An existing destination rule with promotable: false is preserved when a source rule with the same key is promoted:
description: Checkout flowrules: - key: internal-testing promotable: false segments: internal percentage: 100 - key: everyone segments: '*' percentage: 50For rules grouped by environment, matching happens within the same environment. If the project has no environments, matching happens in the flat rules array.
When a filtered promotion calculates dependencies, an omitted source rule does not add the segments used only by that rule. A segment may still be promoted if another selected definition depends on it.
Individual test assertions#
Assertions can use stable keys for item-level protection. If one assertion in a test spec has a key, every assertion in that spec must have a unique key. An assertion that sets promotable must always have a key.
feature: checkoutFlowassertions: - key: production-rollout promotable: false description: Keep the production rollout disabled at: 50 context: userId: production-user expectedToBeEnabled: false - key: general-checkout description: Check the general checkout experience at: 80 context: userId: general-user expectedToBeEnabled: trueA source assertion with promotable: false is omitted. An existing destination assertion with it is preserved when a source assertion with the same key is promoted. If the destination test spec is missing, only promotable source assertions are copied.
Keys must be present in both source and destination test specs when assertion protection is involved. Promotion stops with guidance if only one set uses keys, since matching assertions by position would be unsafe. Existing test specs without assertion keys keep the earlier whole-array promotion behaviour.
A matrix belongs to its parent assertion. Protecting the assertion protects all of its expanded matrix cases together. The same applies to child assertions. Individual matrix cases and child assertions cannot have separate promotion protection.
Assertion keys also give the Catalog stable labels and permalinks for assertions and their expanded matrix cases.
Allowed promotion flows#
By default, any set can be promoted to any other set. To restrict which promotions are allowed, define promotionFlows in your configuration:
module.exports = { sets: true, tags: ['all'], promotionFlows: [ { from: 'dev', to: 'staging' }, { from: 'staging', to: 'production' }, ],}With the configuration above, promote --from=dev --to=production fails because that flow is not listed. Promoting from dev to staging, or from staging to production, is allowed.
Audit files#
Pass --audit to write a record of a promotion. This is useful alongside --apply to keep a trail of what changed:
$ npx featurevisor promote --from=dev --to=staging --apply --audit=markdownAudit files are written only when --apply is present, under .featurevisor/promotions/. Use --audit or --audit=json for machine-readable output, or --audit=markdown for a human-readable summary.
Learn more#
- Sets: split a project into independent trees
- Environments: different rules per feature within a single tree
- State files: where Featurevisor keeps generated state

