
Flags, experiments and remote configuration, declared as YAML in your Git repository.


description: Checkout redesign
bucketBy: userId
rules: - segments: netherlands # Dutch visitors percentage: 100 # enabled for all
- segments: "*" # everyone else percentage: 25 # gradual rolloutf.isEnabled('checkout', context)f.getVariable('supportEmail', context)// → both from memory, no network callA pull request to your repository, a build in your CI, a file on your CDN. There is no vendor in the middle.

Attributes, segments, features and variables are files in one repository. Every change arrives as a pull request and is reviewed like any other.
What lives in the repository
Your CI lints and tests on the pull request, then builds the datafiles on merge and uploads them to your CDN as static JSON.
How datafiles are built
Applications fetch the datafile and evaluate every flag and variable in memory, on the web, on mobile and on the server. No request per lookup.
Pick an SDKOne repository, one pipeline, one SDK call shape. What changes is the kind of answer you get back.
Rules are checked in order and the first match wins. The plain boolean case stays plain.
f.isEnabled('checkout', context)// → trueRollout by rule
Weights divide the audience that already passed the rules, and a user keeps the arm they were given.
f.getVariation('pricing', context)// → 'treatment'Traffic split
Strings, numbers, objects and JSON, checked against a schema when the datafile is built.
f.getVariable('supportEmail', context)// → '[email protected]'Supported types
The percentage is a slice of the audience your rule already matched. Bucketing is deterministic, so the same users stay in as you ramp, and nobody flickers between experiences.
bucketBy: userId
rules: - key: everyone segments: '*' percentage: 10userId 41c7 is in at 10%, and at every step after
Attributes become segments, segments become rules. Every layer is a file you can review, reuse and test on its own.
description: Visitor country
type: string
# your app passes this# as context at runtimeOne typed input, declared once. Anything your app can pass as context: country, plan, app version.
description: Dutch visitors
conditions: - attribute: country operator: equals value: nlNamed, reusable conditions. Written once, referenced everywhere.
rules: - segments: netherlands percentage: 100
- segments: "*" percentage: 25The rule names the segment from step two. Rules are checked in order, and the first match wins.
reused by
Plenty of what you want to change at runtime is a value, not a switch. A global variable is a typed value in a file of its own, with its own lifecycle and its own targeting.
description: Support email shown to users
type: string
f.getVariable('supportEmail')// → '[email protected]'It is addressed by its own key, so no feature has to be opened first and no bucketing has to be passed. Every type the schema system supports is available, all validated when the datafile is built. That is the whole file. Narrowing it comes next.
One default value is rarely the whole story. An override narrows it for the accounts its segment matches, and can carry children that narrow it again, so each level states only what it changes.
type: objectdefaultValue: maxSeats: 5 retentionDays: 30
overrides: - key: paid segments: paidAccounts mutate: maxSeats: 50 overrides: - key: enterprise segments: enterpriseAccounts mutate: retentionDays: 365A child starts from its parent's resolved value, so nothing is restated. Featurevisor flattens the whole tree at build time and ships complete values, which means SDKs never merge anything at runtime.
The same typed values can live inside a feature instead. There they follow whichever variation the user was bucketed into, so each arm of an experiment carries a complete set of its own.
variablesSchema: label: type: string defaultValue: Buy colour: type: string defaultValue: "#111827"
variations: - value: control weight: 50
- value: treatment weight: 50 variables: label: Buy now colour: "#2563eb"f.getVariation('buyButton', context)// → 'treatment'f.getVariable('buyButton', 'label', context)// → 'Buy now'The variation is decided first, by weight, and every variable follows it. An arm never mixes values from another, and anything it does not set falls back to the schema default.
The difference shows up at the call site. Count the keys.
resolved from variation treatment
Use it when the value only makes sense for that feature, or has to follow a variation. It lives and dies with its feature.
f.getVariable('buyButton', 'label')// two keysresolved from override netherlands
Use it when the value has its own lifecycle, or is shared across features and services. It does not need percentage rollout.
f.getVariable('supportEmail')// one keyA feature can require another feature, or one specific variation of it. So can a standalone value. The requirement is checked first, and the rest is never reached.
description: File upload inside chatbucketBy: userId
# there is nothing to upload into# unless live chat is onrequiredFeatures: liveChat
rules: - key: everyone segments: '*' percentage: 100A standalone value can name the same field and wait on a feature in exactly this way. Featurevisor resolves the whole chain at build time, so a datafile never arrives missing something it depends on.
Rules live under an environment key, so staging and production travel together in one reviewed change instead of drifting apart in two dashboards.
Wide open, so the team lives on it every day.
Deliberate, and ramped on its own schedule.
description: Checkout redesignbucketBy: userId
rules: staging: - key: everyone segments: '*' percentage: 100
production: - key: everyone segments: '*' percentage: 10When environments in one file are not enough, sets split the project into independent trees. The same feature key can behave completely differently in each, and you move work forward one promotion at a time.
Each set owns its own attributes, segments, features, targets and tests, exactly like a regular project.
Without --apply it only previews. Applied definitions are ordinary files, so the change still arrives as a Git diff you review, lint and test before it merges.
There is no dashboard where a stray click changes production. Every change travels the same reviewed path your code does.
Your feature management history is your Git history. Every value that ever reached production is attributable to a commit, a reviewer and a build.
Featurevisor ships an official skill, so an agent can author definitions the way you would. It writes the same YAML, into the same repository, and it still has to get past the same gate.
/featurevisor ramp showWishlist to 25%, but only in the Netherlands
Plan
No YAML and no dashboard. Describe the outcome and let the skill supply the vocabulary, whether or not you know the words for it.
rules: - key: netherlands segments: netherlands percentage: 25 - key: everyone segments: '*' percentage: 0A targeted rule ahead of the catch-all, in the order that decides the answer. Ordinary files, in your repository, in your format.
It opens a pull request. An agent cannot reach production any faster than you can, because nothing here bypasses the pipeline.
Test specs are YAML next to your definitions. Pin a bucket value and a context, state the expected answer, and let CI hold you to it.
feature: checkoutassertions: - description: Dutch users are in at: 40 context: country: nl expectedToBeEnabled: true
- description: Everyone else waits at: 90 context: country: de expectedToBeEnabled: falseThe same command runs locally and in CI. A failing assertion exits non zero, so a rollout that would have surprised someone never reaches the datafile.
Your project compiles to static JSON. Applications fetch it once, hold it in memory, and evaluate locally. There is no request to make and nothing to be down.
A target is any runtime that should receive its own datafile. It can take everything, or narrow down by tags and key patterns when an app only needs part of the project.
featurevisor-web.jsonrev 128One file, served from your own CDN. No vendor in the request path, no per seat pricing, and no evaluation service to keep alive.
The same deterministic bucketing in every SDK, so a user bucketed into an experiment on the web is in the same arm on your backend and on their phone, and the same test specs can be run through the SDKs themselves.
Browse the SDKsThe same definitions, targeting and pipeline cover all of them. Nothing here needs a different product.
Ramp a change from one percent to everyone, on your own schedule, with the same users carried forward at every step.
Read the guideWeighted variations with their own variable sets, connected to whichever analytics tool you already run.
Read the guideTyped values that change what the app renders without a release, validated against schemas at build time.
Read the guideMerge to one branch several times a day and let unfinished work sit behind a flag rather than behind a long-lived branch.
Read the guideExpose work in progress to your own team first, then widen, without maintaining a separate build.
Read the guideIndependent teams shipping on independent cadences, from one repository and one set of datafiles.
Read the guideSelf hosted, free, and yours to run. Start a project in one command.