Featurevisor

Define your configuration

Attributes

Attributes declare the context your application passes in, such as a user's country or plan. Conditions in segments, force entries and variable overrides compare against them, and bucketBy uses them to keep each user's result stable.

Featurevisor can only target what your application passes in. Before planning a rollout by plan, region or app version, check that the attribute is declared here and that your application sends it.

Create an attribute

Each attribute is a YAML file in attributes/. The file name is its key, so this file declares country:

attributes/country.yml
description: Country
type: string

description is required. Define either type or oneOf, not both. A few naming rules keep every key usable in every SDK:

  • Attribute files sit directly in attributes/, not in subdirectories. See Namespaces.
  • The key cannot contain dots, even when namespaceCharacter is /. For nested context, declare an object attribute instead.
  • Attribute and property names cannot be names that JavaScript objects inherit, such as constructor or toString. The JavaScript SDK would find a value your context never supplied.

Why declare attributes

Attributes never reach your datafiles. They exist while you author, so mistakes are caught before a change ships. Give plan the values it can take:

attributes/plan.yml
description: Subscription plan
type: string
enum:
- free
- paid
- enterprise

Now suppose a segment misspells one of them:

segments/paidAccounts.yml, with a typo
description: Accounts on the paid plan
conditions:
- attribute: plan
operator: equals
value: piad
Command
npx featurevisor lint
Output
segments/paidAccounts.yml
=> Error: Value does not match the schema of attribute "plan".
Path: conditions.0.value

Linting checks every condition and bucketBy against the attributes you declare. A misspelled attribute gets a suggestion:

Output
=> Error: Unknown attribute "plann". Did you mean "plan"?

An operator that does not suit the type fails too. greaterThan on plan reports that it can only be used with integer or number attributes.

Code generation turns the same declarations into types for your application's context:

Generated types, excerpt
// attributes.ts
export type PlanAttribute = "free" | "paid" | "enterprise";
// context.ts
export interface Context {
country?: CountryAttribute;
deviceId?: DeviceIdAttribute;
plan?: PlanAttribute;
userId?: UserIdAttribute;
[key: AttributeKey]: AttributeValue;
}

Types

Set type to one of the following values:

TypeAccepted valuesExample context entry
booleantrue or falsebetaTester: true
stringTextcountry: 'nl'
integerWhole numbersage: 30
numberNumbers, including decimalsrating: 4.5
arrayArrays of stringspermissions: ['read', 'write']
objectFlat objectsaccount: { locale: 'nl-NL' }

A string can also declare a format for timestamps and versions.

boolean

Use boolean for a yes or no fact, such as whether the user joined a beta programme:

attributes/betaTester.yml
description: Joined the beta programme
type: boolean

Compare it with equals and true or false.

string

Use string for text, such as a country code. This is country from the scaffold:

attributes/country.yml
description: Country
type: string

Strings accept the most operators, from equals and in to startsWith and matches. Add enum to allow only listed values, as plan does above.

integer

Use integer for whole numbers, such as the user's age:

attributes/age.yml
description: Age in years
type: integer

Compare it with greaterThan, lessThanOrEquals and the other number operators.

number

Use number for numbers that can have decimals, such as the average rating a user gives:

attributes/rating.yml
description: Average rating given by the user
type: number

Both 4 and 4.5 are numbers. The number operators work on them as they do on integers.

array

Use array for an array of strings. To limit its values, describe each item with items:

attributes/permissions.yml
description: Permissions
type: array
items:
type: string
enum:
- read
- write
- admin

Array attributes only hold strings. items is required as soon as an array declares enum, const, minItems, maxItems or uniqueItems.

object

Use object for a flat object, and list its properties:

attributes/account.yml
description: Account details
type: object
properties:
locale:
type: string
seats:
type: integer
required:
- locale

Properties are optional unless listed in required. Without required, every property is optional. required only lists keys declared in properties, so an object without properties has no required keys.

Object attributes must stay flat. Their properties cannot be objects. In segment conditions, refer to a property with a dotted path, such as account.locale or account.seats.

Formats

A string attribute can declare a format. Each format changes what lint accepts and which operators it allows. Only these two exist, and any other format is a lint error:

FormatAccepted valuesExample context entry
date-timeISO 8601 timestamps with a timezonesignupDate: '2025-01-15T12:30:00Z'
semverVersions the version operators compareversion: '5.5.0'

date-time

Use format: date-time for a timestamp with a timezone, such as 2025-01-15T12:30:00Z or 2025-01-15T13:30:00+01:00:

attributes/signupDate.yml
description: Signup date
type: string
format: date-time

Compare timestamps with before and after. The only other permitted operators are exists, notExists, isNull and notNull. Equality, string matching and emptiness operators are rejected, including on timestamp properties inside objects, because one instant has many spellings.

Every enum entry and const must be a portable timestamp. Date only strings and epoch numbers are not timestamps. Code generation uses Date | string for an unconstrained timestamp attribute, since context can hold a native date. See native date inputs for other SDKs.

To compare a timestamp as text too, for example with equals, declare it as a plain string without a format. before and after still work on plain strings, but lint stops checking that values are timestamps. Do not combine both in oneOf. A valid timestamp matches both branches, and oneOf needs exactly one.

semver

Use format: semver for a version string such as 5.5.0:

attributes/version.yml
description: Version number of the app
type: string
format: semver

Compare versions with the semantic version operators:

segments/supportedVersion.yml
description: App version 5.5.0 or later
conditions:
- attribute: version
operator: semverGreaterThanOrEquals
value: '5.5.0'

The format accepts every version these operators can compare, not only strict semantic versions. 5.5, v5.5.0 and 5.x are valid, while 5.5,0 is not. Lint checks enum, const and condition values against it, so a version that could never match fails before it ships. The version operators also work on plain strings.

Quote versions in YAML. Unquoted, 5.5 is read as a number. Pass the version as a string in runtime context too, for example { version: '5.5.0' }.

oneOf

Use oneOf instead of a top level type when an attribute must match exactly one of several schemas. It needs at least two:

attributes/externalId.yml
description: External identifier supplied as text or an integer
oneOf:
- type: string
- type: integer

Constrain values

These properties narrow what an attribute accepts. Linting checks condition values against all of them. Code generation turns enum and const into exact types and makes required properties non-optional.

PropertiesApplies toPurpose
enum, constAny typeAllow only listed values, or a single value
minLength, maxLength, patternstringLimit length or match a regular expression
formatstringAccept only timestamps or versions
minimum, maximuminteger and numberLimit the numeric range
items, minItems, maxItems, uniqueItemsarrayDescribe items and limit their count
properties, required, additionalPropertiesobjectDeclare keys and which must be present

enum and const values must match type and format, and required can only list keys from properties. Lengths and item counts are whole numbers of zero or more. Schemas describes each property in detail.

Context

An SDK evaluates against the context your application passes. Its keys are attribute keys, and object attributes are passed as objects:

const context = {
userId: '8f2a',
country: 'nl',
plan: 'paid',
account: {
locale: 'nl-NL',
seats: 12,
},
}
f.isEnabled('checkout', context)
  • Keys that no condition or bucketBy uses have no effect.
  • When an attribute is missing from the context, most operators return false. notEquals and the presence operators are the exceptions listed in Operators.
  • SDKs do not check context types at runtime, because attributes are not in datafiles. In TypeScript, use the generated Context type above.

Lint warns about test contexts that do not match any declared attribute type or constraint. Warnings identify the received value and its context path. CLI evaluate gives the same nonblocking warnings for supplied context. Neither changes the evaluation result, and SDKs do not emit these warnings. See Context warnings.

Archiving

Set archived: true on an attribute you no longer use:

attributes/deviceId.yml
archived: true
description: Device ID
type: string

An archived attribute is left out of generated code. Linting stops an active segment, feature or global variable from still using it, including in bucketBy, so find and remove its references first. Archived definitions that use it do not count. Keep the file rather than deleting it, so the key is not reused with another meaning. You can list archived attributes afterwards:

Commands
npx featurevisor find-usage --attribute=deviceId
npx featurevisor list --attributes --archived

Promotable

In a project that uses sets, an attribute can protect its existing destination definition from later promotions by setting promotable: false at the top level:

sets/production/attributes/country.yml
description: Country
promotable: false
type: string

If the destination attribute exists, it remains unchanged when either the source or destination attribute has this field. A missing destination attribute is still created and retains promotable: false. The field does not apply to nested attribute schema properties.

Previous
Quick start