The Flagdeck Vue SDK provides an easy way to use feature flags in Vue applications. Built on top of the JavaScript SDK, it offers Vue-specific components and composables for a more idiomatic integration with your Vue projects.
Install the Vue SDK using npm, yarn, or pnpm:
# Using npm
npm install @flagdeck/vue
# Using yarn
yarn add @flagdeck/vue
# Using pnpm
pnpm add @flagdeck/vue
Add the Flagdeck plugin to your Vue application:
// main.ts
import { createApp } from 'vue';
import App from './App.vue';
import { createFlagdeckPlugin } from '@flagdeck/vue';
const app = createApp(App);
app.use(
createFlagdeckPlugin({
clientOptions: {
apiKey: 'YOUR_API_KEY',
realTimeUpdates: true,
},
context: { // Changed from initialUserContext
userId: 'user-123',
attributes: {
plan: 'free'
}
}
})
);
app.mount('#app');
Then use Flagdeck in your components:
<template>
<div>
<h1>Feature Flag Example</h1>
<div v-if="isLoading">Loading...</div>
<div v-else>
{{ isEnabled ? 'New feature is enabled!' : 'New feature is disabled' }}
</div>
</div>
</template>
<script setup>
import { useFlag } from '@flagdeck/vue';
const { isEnabled, isLoading } = useFlag('new-feature', false);
</script>
The Vue plugin initializes the Flagdeck SDK and makes it available throughout your application.
app.use(
createFlagdeckPlugin({
clientOptions: {
apiKey: 'your-api-key', // Required
debug: true, // Optional: enables debug logging
realTimeUpdates: true, // Optional: enables real-time flag updates via SSE
timeout: 5000, // Optional: API request timeout in ms
enableCache: true, // Optional: enables caching of flag evaluations
cacheTimeout: 30000, // Optional: cache TTL in ms
enableOfflineMode: false, // Optional: enables offline fallback
enableAnalytics: true, // Optional: enables analytics tracking
},
context: { // Changed from initialUserContext
userId: 'user-123', // User identifier
attributes: { // Targeting attributes
country: 'US',
plan: 'premium'
}
}
})
);
For more detailed information about all available configuration options, refer to the JavaScript SDK API Reference.
Access the Flagdeck client and context:
<template>
<div>
<div v-if="!isReady">Loading SDK...</div>
<div v-else-if="error">Error: {{ error.message }}</div>
<div v-else>
<select
:value="context?.attributes?.plan || 'free'" <!-- Changed from userContext -->
@change="handlePlanChange"
>
<option value="free">Free Plan</option>
<option value="premium">Premium Plan</option>
</select>
</div>
</div>
</template>
<script setup>
import { useFlagdeck } from '@flagdeck/vue';
const {
client, // Flagdeck client instance
isReady, // True when SDK is initialized and ready
error, // Error object if SDK initialization failed
context, // Current targeting context (Changed from userContext)
setContext // Function to update context (Changed from setUserContext)
} = useFlagdeck();
const handlePlanChange = (event) => {
const plan = event.target.value;
setContext({ // Changed from setUserContext
...context, // Changed from userContext
attributes: {
...context?.attributes, // Changed from userContext
plan
}
});
};
</script>
Evaluate a boolean feature flag:
<template>
<div>
<div v-if="isLoading">Loading...</div>
<div v-else-if="error">Error: {{ error.message }}</div>
<div v-else>
<component :is="isEnabled ? NewDashboard : LegacyDashboard" />
</div>
</div>
</template>
<script setup>
import { useFlag } from '@flagdeck/vue';
import NewDashboard from './NewDashboard.vue';
import LegacyDashboard from './LegacyDashboard.vue';
const {
isEnabled, // Boolean flag value
isLoading, // True when flag is being evaluated
error // Error object if evaluation failed
} = useFlag('new-dashboard', false); // 'false' is the default value
</script>
Get a feature flag value of any type:
<template>
<div :style="style">
Current theme: {{ isLoading ? 'Loading...' : theme }}
</div>
</template>
<script setup>
import { computed } from 'vue';
import { useFlagValue } from '@flagdeck/vue';
const {
value: theme, // Flag value (can be any type)
isLoading, // True when flag is being evaluated
error // Error object if evaluation failed
} = useFlagValue('theme-variant', 'default'); // 'default' is the default value
// Apply different styles based on theme value
const themeStyles = {
default: { background: 'white', color: 'black' },
dark: { background: 'black', color: 'white' },
blue: { background: '#0070F3', color: 'white' }
};
const style = computed(() => themeStyles[theme.value] || themeStyles.default);
</script>
Evaluate multiple flags at once:
<template>
<div>
<div v-if="isLoading">Loading features...</div>
<div v-else>
<p>Enabled features: {{ enabledCount }}/{{ totalCount }}</p>
<NewHeader v-if="flagValues['new-header']" />
<Sidebar v-if="flagValues['sidebar']" />
<AnalyticsPanel v-if="flagValues['analytics']" />
<!-- Error handling -->
<div
v-for="(error, key) in errors"
:key="key"
class="error"
>
Error with {{ key }}: {{ error.message }}
</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
import { useFlags } from '@flagdeck/vue';
import NewHeader from './NewHeader.vue';
import Sidebar from './Sidebar.vue';
import AnalyticsPanel from './AnalyticsPanel.vue';
const {
values, // Record of flag keys to their values
isLoading, // True when flags are being evaluated
errors // Record of flag keys to error objects
} = useFlags(['new-header', 'sidebar', 'analytics'], false);
// Reactive references to flag values
const flagValues = values;
// Computed properties
const enabledCount = computed(() =>
Object.values(flagValues.value).filter(Boolean).length
);
const totalCount = computed(() =>
Object.keys(flagValues.value).length
);
</script>
Conditionally render content based on flag state:
<template>
<div>
<h1>Dashboard</h1>
<FeatureFlag
flag-key="new-dashboard"
:default-enabled="false"
@error="handleError"
>
<template #default>
<NewDashboard />
</template>
<template #fallback>
<LegacyDashboard />
</template>
<template #loading>
<div>Loading dashboard...</div>
</template>
</FeatureFlag>
</div>
</template>
<script setup>
import { FeatureFlag } from '@flagdeck/vue';
import NewDashboard from './NewDashboard.vue';
import LegacyDashboard from './LegacyDashboard.vue';
function handleError(error) {
console.error('Dashboard flag error:', error);
}
</script>
Scoped slot component for accessing flag values:
<template>
<FlagValue flag-key="color-theme" :default-value="'default'">
<template #default="{ value: theme, isLoading }">
<!-- Apply theme classes or styles based on the theme value -->
<div :class="`theme-${isLoading ? 'default' : theme}`">
<div v-if="isLoading" class="theme-loading-indicator"></div>
<slot></slot>
</div>
</template>
</FlagValue>
</template>
<script setup>
import { FlagValue } from '@flagdeck/vue';
</script>
<style scoped>
.theme-default {
background-color: white;
color: black;
}
.theme-dark {
background-color: #121212;
color: white;
}
.theme-blue {
background-color: #0070F3;
color: white;
}
.theme-loading-indicator {
height: 2px;
background-color: #0070F3;
width: 100%;
position: absolute;
top: 0;
left: 0;
animation: loading 1s infinite;
}
@keyframes loading {
0% { width: 0%; }
50% { width: 50%; }
100% { width: 100%; }
}
</style>
Scoped slot alternative for conditional rendering:
<template>
<div>
<h2>Payment Options</h2>
<!-- Always shown payment methods -->
<CreditCardOption />
<PayPalOption />
<!-- Conditionally shown new payment methods -->
<WithFeature flag-key="crypto-payments">
<template #default="{ isEnabled, isLoading }">
<div v-if="isLoading">Loading payment options...</div>
<CryptoPaymentOption v-else-if="isEnabled" />
</template>
</WithFeature>
</div>
</template>
<script setup>
import { WithFeature } from '@flagdeck/vue';
import CreditCardOption from './CreditCardOption.vue';
import PayPalOption from './PayPalOption.vue';
import CryptoPaymentOption from './CryptoPaymentOption.vue';
</script>
The Vue SDK supports real-time updates via Server-Sent Events (SSE). When enabled, flag changes made in the Flagdeck dashboard will be pushed to your application immediately without requiring page refreshes.
app.use(
createFlagdeckPlugin({
clientOptions: {
apiKey: 'your-api-key',
realTimeUpdates: true // Enable real-time updates
}
})
);
Then in your components, the composables will automatically update with new values when flags change:
<template>
<div>
Feature status: {{ isEnabled ? 'Enabled' : 'Disabled' }}
</div>
</template>
<script setup>
import { useFlag } from '@flagdeck/vue';
// This will automatically update when the flag changes
const { isEnabled } = useFlag('monitored-feature');
</script>
Set or update context to target features to specific users or environments:
<template>
<div>
<h2>User Profile</h2>
<select
:value="context?.attributes?.plan || 'free'" <!-- Changed from userContext -->
@change="handlePlanChange"
>
<option value="free">Free Plan</option>
<option value="pro">Pro Plan</option>
<option value="enterprise">Enterprise Plan</option>
</select>
<!-- This component will re-evaluate flags when context changes -->
<FeaturePanel />
</div>
</template>
<script setup>
import { useFlagdeck } from '@flagdeck/vue';
import FeaturePanel from './FeaturePanel.vue';
const { context, setContext } = useFlagdeck(); // Changed from userContext, setUserContext
const handlePlanChange = (event) => {
const plan = event.target.value;
setContext({ // Changed from setUserContext
...context, // Changed from userContext
attributes: {
...context?.attributes, // Changed from userContext
plan
}
});
};
</script>
All composables provide error information that you can use to handle different error scenarios:
<template>
<div>
<!-- Handle different error types -->
<div v-if="error">
<div v-if="error.type === 'notfound'">
This feature is not configured yet.
</div>
<div v-else-if="error.type === 'inactive'">
This feature is currently disabled.
</div>
<div v-else-if="error.type === 'configuration'">
There's a configuration issue with this feature.
</div>
<div v-else>
Error loading feature: {{ error.message }}
</div>
</div>
<!-- Normal rendering when no errors -->
<div v-else>
<div v-if="isEnabled">
Beta feature is enabled!
</div>
<div v-else>
Beta feature is disabled.
</div>
</div>
</div>
</template>
<script setup>
import { useFlag } from '@flagdeck/vue';
const { isEnabled, error } = useFlag('beta-feature');
</script>
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
import AccessDenied from '../views/AccessDenied.vue';
// Create the route guard factory
function createFeatureFlagGuard(flagdeck, flagKey, fallbackPath = '/access-denied') {
return async (to, from) => {
const { client } = flagdeck;
if (!client) {
return { path: fallbackPath };
}
try {
const isEnabled = await client.isEnabled(flagKey);
if (!isEnabled) {
return { path: fallbackPath };
}
} catch (err) {
console.error(`Error checking feature flag: ${flagKey}`, err);
return { path: fallbackPath };
}
};
}
// Create the router
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
component: Home
},
{
path: '/beta-feature',
component: () => import('../views/BetaFeature.vue'),
// We'll add the navigation guard in the main.ts
},
{
path: '/access-denied',
component: AccessDenied
}
]
});
export { router, createFeatureFlagGuard };
// main.ts
import { createApp } from 'vue';
import App from './App.vue';
import { createFlagdeckPlugin, useFlagdeck } from '@flagdeck/vue';
import { router, createFeatureFlagGuard } from './router';
const app = createApp(App);
// Create the Flagdeck plugin
const flagdeckPlugin = createFlagdeckPlugin({
clientOptions: {
apiKey: 'YOUR_API_KEY'
}
});
// Add the plugin
app.use(flagdeckPlugin);
app.use(router);
// Get an instance of the Flagdeck context
const flagdeck = useFlagdeck();
// Add navigation guards after Flagdeck is initialized
router.beforeEach(async (to) => {
if (to.path === '/beta-feature') {
return createFeatureFlagGuard(flagdeck, 'beta-access')(to, undefined);
}
});
app.mount('#app');
<template>
<div>
<div v-if="isLoading">Loading feature flags...</div>
<Suspense v-else>
<template #default>
<component :is="currentComponent" />
</template>
<template #fallback>
<div>Loading component...</div>
</template>
</Suspense>
</div>
</template>
<script setup>
import { computed, defineAsyncComponent } from 'vue';
import { useFlag } from '@flagdeck/vue';
// Define async components
const NewComponent = defineAsyncComponent(() => import('./NewComponent.vue'));
const LegacyComponent = defineAsyncComponent(() => import('./LegacyComponent.vue'));
const { isEnabled, isLoading } = useFlag('new-component');
// Choose which component to render based on flag
const currentComponent = computed(() =>
isEnabled.value ? NewComponent : LegacyComponent
);
</script>
<!-- ThemeProvider.vue -->
<template>
<slot></slot>
</template>
<script setup>
import { provide, computed, watch } from 'vue';
import { useFlagValue } from '@flagdeck/vue';
// Create theme key for provide/inject
const ThemeKey = Symbol('ThemeContext');
// Get theme from flag
const { value: themeValue } = useFlagValue('app-theme', 'default');
// Compute theme properties
const theme = computed(() => {
const current = themeValue.value;
return {
name: current,
isDark: current === 'dark' || current === 'midnight',
colors: getThemeColors(current),
};
});
// Provide theme context to descendant components
provide(ThemeKey, theme);
// Apply theme to document when it changes
watch(theme, (newTheme) => {
document.documentElement.setAttribute('data-theme', newTheme.name);
if (newTheme.isDark) {
document.documentElement.classList.add('dark-mode');
} else {
document.documentElement.classList.remove('dark-mode');
}
}, { immediate: true });
// Helper function to get theme colors
function getThemeColors(themeName) {
switch (themeName) {
case 'dark':
return {
background: '#121212',
text: '#ffffff',
primary: '#0070F3'
};
case 'blue':
return {
background: '#f0f8ff',
text: '#333333',
primary: '#0070F3'
};
default:
return {
background: '#ffffff',
text: '#333333',
primary: '#0070F3'
};
}
}
// Export composition function for components to use theme
export function useTheme() {
return { theme };
}
</script>
<!-- Usage in App.vue -->
<template>
<ThemeProvider>
<YourApp />
</ThemeProvider>
</template>
<script setup>
import ThemeProvider from './ThemeProvider.vue';
import YourApp from './YourApp.vue';
</script>
<!-- Usage in a component -->
<template>
<div :style="{
backgroundColor: theme.colors.background,
color: theme.colors.text
}">
<h1>Current theme: {{ theme.name }}</h1>
</div>
</template>
<script setup>
import { useTheme } from './ThemeProvider.vue';
const { theme } = useTheme();
</script>
The Vue SDK is built with TypeScript and provides full type definitions:
<template>
<div>
<p>Feature enabled: {{ String(isEnabled) }}</p>
<p>Theme: {{ theme }}</p>
<p>Rate limit: {{ limit }}</p>
<p>API timeout: {{ config.timeout }}ms</p>
</div>
</template>
<script setup lang="ts">
import { useFlag, useFlagValue } from '@flagdeck/vue';
// Boolean flag
const { isEnabled } = useFlag('feature-flag');
// String flag with specific type
const { value: theme } = useFlagValue<'light' | 'dark' | 'system'>('theme', 'system');
// Number flag
const { value: limit } = useFlagValue<number>('rate-limit', 100);
// Complex object flag
interface ConfigType {
timeout: number;
retries: number;
endpoints: string[];
}
const defaultConfig: ConfigType = {
timeout: 3000,
retries: 3,
endpoints: ['api.example.com']
};
const { value: config } = useFlagValue<ConfigType>('api-config', defaultConfig);
</script>
Initialize Early: Add the Flagdeck plugin as early as possible in your Vue application's initialization to make flags available throughout your app.
Handle Loading States: Always check isLoading
before using flag values to avoid flash of incorrect content.
Provide Default Values: Always specify default values that make sense for your application as fallbacks.
Batch Flag Evaluations: Use useFlags
to evaluate multiple flags at once for better performance.
Consider SSR: For Nuxt.js and other SSR frameworks, provide sensible defaults and revalidate flags on the client side.
Keep Context Updated: Always update context when targeting attributes change to ensure correct targeting.
Use Component Composition: Compose feature-gated components to create clean abstractions in your application.
Cache Key Flags: Consider saving critical flags to local storage for offline use.
Minimize Reactivity Overhead: Use useFlagdeck
only in components that need to access or update the context.
Optimize Flag Access: Use useFlags
for multiple flags instead of multiple useFlag
calls.
Enable Caching: Keep the default caching enabled to reduce API calls.
Use Real-time Updates Wisely: Only enable real-time updates for flags that need immediate propagation.
Consider Component Structure: Place flag evaluation in component setup, not in computed properties or watchers where possible.
For more detailed information about the underlying JavaScript SDK features and configuration options, refer to the JavaScript SDK API Reference.
Start using Flagdeck today
Simple feature flag management for modern development teams.
Product
© 2025 Flagdeck. All rights reserved.