Framework guides
Vue.js SDK
@featurevisor/vue gives components composables that read features and variables from a shared JavaScript SDK instance, and update when their values change.
Install#
npm install @featurevisor/vue @featurevisor/sdkSet up the application#
Create an instance and give it to the application with setupApp:
import { createApp } from 'vue'import { createFeaturevisor } from '@featurevisor/sdk'import { setupApp } from '@featurevisor/vue'import App from './App.vue'const response = await fetch( 'https://cdn.example.com/featurevisor-all.json',)if (!response.ok) { throw new Error(`Could not fetch datafile: HTTP ${response.status}`)}const f = createFeaturevisor({ datafile: await response.json(), context: { userId: 'user-4', country: 'nl', },})const app = createApp(App)setupApp(app, f)app.mount('#app')Every composable below reads this instance, with its context.
Composables#
Each composable returns a readonly ref. A template uses it directly, since Vue unwraps refs there. In script, read its .value. It updates when a new datafile, context or sticky values change it, as When values update describes. Call composables inside setup.
useFlag#
Returns whether a flag is enabled:
<script setup> import { useFlag } from '@featurevisor/vue' const newCheckout = useFlag('checkout')</script><template> <NewCheckout v-if="newCheckout" /> <Checkout v-else /></template>useFlags#
Returns whether each flag in a list is enabled, in the order of the keys. Name each value after its key, since only its position ties it to the key:
<script setup> import { useFlags } from '@featurevisor/vue' const [checkout, liveChat, wishlist] = useFlags([ 'checkout', 'liveChat', 'wishlist', ])</script><template> <nav> <CheckoutLink v-if="checkout" /> <ChatButton v-if="liveChat" /> <WishlistLink v-if="wishlist" /> </nav></template>It returns one ref per key, so the result can be destructured. The list can have any length, including one built at runtime, and is read once when the composable is called. A feature missing from the datafile is false. In TypeScript, a list written inline gives a tuple of refs of exactly that length, and a list built at runtime gives an array:
const flags = useFlags(['checkout', 'liveChat'])// readonly [Readonly<Ref<boolean>>, Readonly<Ref<boolean>>]useEveryFlag#
Returns true when every flag in a list is enabled:
<script setup> import { useEveryFlag } from '@featurevisor/vue' const savedForCheckout = useEveryFlag(['checkout', 'wishlist'])</script><template> <SavedItems v-if="savedForCheckout" /></template>It evaluates in order and stops at the first disabled flag. Flags after that are not evaluated, so a module that records exposure does not see them. Like JavaScript's every, an empty list holds true. In TypeScript, an empty list written inline is an error.
When one feature must never be on without another, require it in the feature's definition instead, as in Managing feature dependencies. Then every application and SDK agrees, not only this component.
useSomeFlag#
Returns true when at least one flag in a list is enabled:
<script setup> import { useSomeFlag } from '@featurevisor/vue' const showHelp = useSomeFlag(['liveChat', 'wishlist'])</script><template> <HelpMenu v-if="showHelp" /></template>It evaluates in order and stops at the first enabled flag, so a module that records exposure does not see the flags after it. Like JavaScript's some, an empty list holds false. In TypeScript, an empty list written inline is an error.
useVariation#
Returns a feature's variation, or null:
<script setup> import { useVariation } from '@featurevisor/vue' const variation = useVariation('pricing')</script><template> <NewPricing v-if="variation === 'treatment'" /> <CurrentPricing v-else /></template>In TypeScript, name the values it can return:
type PricingVariation = 'control' | 'treatment'const variation = useVariation<PricingVariation>('pricing')// Readonly<Ref<PricingVariation | null>>useVariable#
With a feature key and a variable key, returns a feature variable. For user-4, the buy button says Nu kopen:
<script setup> import { useVariable } from '@featurevisor/vue' const label = useVariable('buyButton', 'label') const colour = useVariable('buyButton', 'colour')</script><template> <button :style="{ background: colour }">{{ label }}</button></template>With one key, it returns a global variable:
const supportEmail = useVariable('supportEmail')In TypeScript, pass the type you expect. It is not checked at runtime. For types that match your project, use code generation:
type CheckoutSettings = { currency: string paymentMethods: string[]}const settings = useVariable<CheckoutSettings>('checkoutSettings')// Readonly<Ref<CheckoutSettings | null>>useRevision#
Returns the revision of the instance's datafile:
<script setup> import { useRevision } from '@featurevisor/vue' const revision = useRevision()</script><template> <small>Datafile revision {{ revision }}</small></template>It updates only when a datafile with a different revision is set. Before a datafile is set, it holds unknown.
useSdk#
useSdk returns the instance itself, for anything the other composables do not cover. Call it, like them, inside setup. What you read through it does not update. For a debugging panel, getFeatureEvaluations() on the instance gives a snapshot of several features at once.
Add context in a component#
Every composable that reads a feature or variable takes context as its last argument, merged over the instance context for that read:
const context = { plan: 'paid',}const colour = useVariable('buyButton', 'colour', context)// '#047857'To follow props or other reactive state, pass a ref or a getter. The value updates when what it reads changes:
<script setup> import { useVariable } from '@featurevisor/vue' const props = defineProps({ plan: String }) const context = () => ({ plan: props.plan }) const label = useVariable('buyButton', 'label') const colour = useVariable('buyButton', 'colour', context)</script><template> <button :style="{ background: colour }">{{ label }}</button></template>Scopes#
FeaturevisorScope adds context for every composable in the components inside it. A part of the page adds what only it knows, such as the product it shows, and its composables read features with it, without passing context to each call.
Scope part of the page#
A product page knows which product it shows. In setup, provideFeaturevisorScope adds the product's ID for the components below:
<script setup> import { provideFeaturevisorScope } from '@featurevisor/vue' import ProductDetails from '../components/ProductDetails.vue' import ProductReviews from '../components/ProductReviews.vue' const props = defineProps({ product: Object }) provideFeaturevisorScope(() => ({ productId: props.product.id }))</script><template> <ProductDetails :product="product" /> <ProductReviews /></template><script setup> import { useFlag } from '@featurevisor/vue' const productReviews = useFlag('productReviews')</script><template> <ReviewList v-if="productReviews" /></template>provideFeaturevisorScope applies to the components below, not to the composables of the component that calls it, so the reviews read the flag in their own component. The getter follows the prop, so moving to another product updates the scope without remounting the page.
Every composable below the scope evaluates with productId, so a feature's conditions can target products as they would any other attribute.
Microfrontends that share one instance through a host application can use a scope the same way, each naming itself in its context, so rules can target where a feature appears.
The scope adds to the instance context rather than replacing it, so these composables still read the user's userId and country. How contexts merge gives the full order.
Inside a scope, every composable that reads a feature or variable uses it, while useSdk and useRevision are unchanged.
A scope that sets userId changes the bucket its composables use, so set it only when that part of the page is about another user, as in a support tool. A tracking module sees the merged context, so it can tell which product an exposure came from.
Change a scope#
A scope follows its context prop, and provideFeaturevisorScope follows a ref, a getter or a reactive object, including changes inside them. When the values change, the composables below evaluate again, and their components update with any value that changed. In this quick view, picking another product changes the scope:
<script setup> import { ref } from 'vue' import { FeaturevisorScope } from '@featurevisor/vue' import ProductPicker from './ProductPicker.vue' import ProductReviews from './ProductReviews.vue' const props = defineProps({ products: Array }) const product = ref(props.products[0])</script><template> <ProductPicker :products="products" @pick="product = $event" /> <FeaturevisorScope :context="{ productId: product.id }"> <ProductReviews /> </FeaturevisorScope></template>An update with the same values, such as the new inline object this template creates on every render, evaluates nothing again, and that holds for nested objects, arrays and dates too.
Nested scopes#
A scope inside another merges over it, so each part adds what it knows to what surrounds it. In a template, the FeaturevisorScope component adds one. A category page sets the category once, and each product card adds its own product:
<script setup> import { FeaturevisorScope } from '@featurevisor/vue' import ProductCard from '../components/ProductCard.vue' defineProps({ category: Object, products: Array })</script><template> <FeaturevisorScope :context="{ category: category.key }"> <ul> <FeaturevisorScope v-for="product in products" :key="product.id" :context="{ productId: product.id }" > <ProductCard :product="product" /> </FeaturevisorScope> </ul> </FeaturevisorScope></template><script setup> import { useFlag } from '@featurevisor/vue' defineProps({ product: Object }) const sizeGuide = useFlag('sizeGuide') const productReviews = useFlag('productReviews')</script><template> <li> {{ product.name }} <SizeGuideLink v-if="sizeGuide" /> <ReviewSummary v-if="productReviews" :product="product" /> </li></template>Each card's composables evaluate with category from the page and their own productId, so sizeGuide can target the category while productReviews targets the product.
provideFeaturevisorScope nests the same way as the component. An inner scope keeps the keys it sets when the outer scope changes them, and follows the outer scope for the rest, so an outer scope that follows a ref or prop updates everything inside it. Sibling scopes do not affect each other, and changing an inner scope does not update the composables that read only the outer one.
How contexts merge#
Before a composable reads a feature or variable, it merges every context that applies to it into one and evaluates with that. It merges them in this order, each over the ones before it:
- the instance context, set by
createFeaturevisorandsetContext - each enclosing scope, from the outermost to the innermost, whether
provideFeaturevisorScopeor theFeaturevisorScopecomponent added it - the context passed to the composable, as in Add context in a component
A key that only one of them sets passes through. A key that more than one sets takes its value from the last.
On the category page for shoes, with the instance from Set up the application, the card for product 1 evaluates sizeGuide and productReviews with the instance's userId and country, the page's category and its own productId:
{ userId: 'user-4', country: 'nl', category: 'shoes', productId: '1',}The order follows where a context sits, not when it changed. A scope keeps the keys it sets even after setContext changes them on the instance, and an inner scope keeps its keys over an outer one. A product page that lists related products can wrap each in a scope of its own: inside it, productId is the related product's, and everywhere else on the page it is the page's.
The merge is shallow, like the instance's own context. A scope that sets an object attribute, such as account, replaces the instance's account as a whole rather than merging the two objects' keys.
The merged context is not stored. The instance keeps its own, and each read merges again. Sticky values sit outside the merge: a feature with a sticky value returns it whatever the merged context holds.
When values update#
Every composable that reads a feature or variable updates its ref when:
- a new datafile affects its key
- the instance context changes
- sticky values change
- a ref or prop its context reads changes
- the context of an enclosing scope changes
A datafile update names the features and global variables it affects, including those whose conditions depend on a changed segment, feature or variable, and a composable ignores the rest. A composable that takes a list updates when the update affects any feature in it. useRevision updates only when the revision changes.
A ref evaluates when it is first read, and again only after one of these changes, so rendering a component again does not evaluate it again. Composables subscribe when their component mounts and unsubscribe when it unmounts. During server rendering, they evaluate without subscribing.
Tracking#
To record when a user sees a feature, add a module to the instance and act in its afterEvaluation.
Each ref evaluates when it is first read and after every change, so the module sees a feature again in every component that reads it and after every update. Record an exposure once per feature and user, not once per evaluation. Google Analytics has a complete example that does this.


