Vue
This guide shows you how to integrate Basestack Feature Flags into Vue 3 applications using the JavaScript SDK (@basestack/flags-js). You'll build a custom composable and plugin that provides feature flags throughout your Vue application using the Composition API.
Overview
This guide will help you build:
- FeatureFlags Plugin: A Vue plugin that provides the SDK instance globally
- useFlag Composable: A composable to fetch and use individual flags reactively
- useFlags Composable: A composable to fetch and use all flags reactively
- Server Utilities: Helper functions for server-side usage (Nuxt, SSR)
The implementation uses Vue's Composition API and reactivity system to provide reactive flag state throughout your application.
Getting Started
Install Dependencies
Install the core JavaScript SDK. Vue 3 works perfectly with the JavaScript SDK.
npm install @basestack/flags-jspnpm install @basestack/flags-jsyarn add @basestack/flags-jsbun add @basestack/flags-jsEnvironment Variables
Configure your environment variables. The prefix depends on your build tool (Vite uses VITE_, Nuxt uses NUXT_PUBLIC_, etc.).
When it comes to environment variables, pay attention to the framework you're
using. For example, NUXT_PUBLIC_ is specific to Nuxt, while in
Vite.js, it would be VITE_.
# BASESTACK FEATURE FLAGS
VITE_FEATURE_FLAGS_BASE_URL="https://flags-api.basestack.co/v1"
VITE_FEATURE_FLAGS_PROJECT_KEY=""
VITE_FEATURE_FLAGS_ENVIRONMENT_KEY=""You can find your project and environment keys in your Basestack Feature Flags Dashboard.
Create Project Structure
Create a folder structure to organize your feature flags implementation:
File Structure:
plugins/feature-flags.ts- Vue plugin for global SDK instancecomposables/useFlag.ts- Composable for individual flagscomposables/useFlags.ts- Composable for all flags
Implementation Guide
Step 1: Create the Feature Flags Plugin
Create a Vue plugin that initializes the SDK and makes it available globally:
import type { App } from "vue";
import { FlagsSDK, type SDKConfig } from "@basestack/flags-js";
// Create SDK instance
export function createFlagsClient(config: SDKConfig): FlagsSDK {
return new FlagsSDK({
baseURL: config.baseURL,
projectKey: config.projectKey,
environmentKey: config.environmentKey,
});
}
// Plugin installation
export default {
install(app: App, config: SDKConfig) {
const client = createFlagsClient(config);
// Make SDK available globally via provide/inject
app.provide("flagsClient", client);
// Also add to global properties for convenience
app.config.globalProperties.$flags = client;
},
};Key Features:
- Initializes SDK instance on plugin installation
- Provides SDK via Vue's
provide/injectsystem - Also adds to global properties for template access
Step 2: Create the useFlag Composable
Create a composable that reactively fetches and returns a single flag:
import { ref, computed, inject, onMounted, type Ref } from "vue";
import { FlagsSDK, type Flag } from "@basestack/flags-js";
export function useFlag<P = Record<string, unknown>>(slug: string) {
const client = inject<FlagsSDK>("flagsClient");
if (!client) {
throw new Error("Feature flags plugin not installed. Make sure to install the plugin in main.ts");
}
const flag: Ref<Flag | null> = ref(null);
const error: Ref<Error | null> = ref(null);
const isLoading = ref(true);
const enabled = computed(() => flag.value?.enabled ?? false);
const payload = computed(() => (flag.value?.payload as P) ?? null);
const fetchFlag = async () => {
try {
isLoading.value = true;
error.value = null;
const result = await client.getFlag(slug);
flag.value = result;
} catch (err) {
error.value = err instanceof Error ? err : new Error(String(err));
flag.value = null;
} finally {
isLoading.value = false;
}
};
onMounted(() => {
fetchFlag();
});
return {
flag: readonly(flag),
enabled,
payload,
isLoading: readonly(isLoading),
error: readonly(error),
refresh: fetchFlag,
};
}Usage:
<script setup lang="ts">
import { useFlag } from "@/composables/useFlag";
const { enabled, payload, isLoading } = useFlag<{ variant: string }>("header");
</script>Step 3: Create the useFlags Composable
Create a composable that reactively fetches and returns all flags:
import { ref, computed, inject, onMounted, readonly, type Ref } from "vue";
import { FlagsSDK, type Flag } from "@basestack/flags-js";
export function useFlags() {
const client = inject<FlagsSDK>("flagsClient");
if (!client) {
throw new Error("Feature flags plugin not installed. Make sure to install the plugin in main.ts");
}
const flags: Ref<Flag[]> = ref([]);
const error: Ref<Error | null> = ref(null);
const isLoading = ref(true);
const flagsBySlug = computed(() => {
return flags.value.reduce((acc, flag) => {
acc[flag.slug] = flag;
return acc;
}, {} as Record<string, Flag>);
});
const fetchFlags = async () => {
try {
isLoading.value = true;
error.value = null;
const result = await client.getAllFlags();
flags.value = result.flags;
} catch (err) {
error.value = err instanceof Error ? err : new Error(String(err));
flags.value = [];
} finally {
isLoading.value = false;
}
};
onMounted(() => {
fetchFlags();
});
return {
flags: readonly(flags),
flagsBySlug,
isLoading: readonly(isLoading),
error: readonly(error),
refresh: fetchFlags,
};
}Usage:
<script setup lang="ts">
import { useFlags } from "@/composables/useFlags";
const { flags, isLoading } = useFlags();
</script>Step 4: Install the Plugin
Install the plugin in your main application file:
import { createApp } from "vue";
import App from "./App.vue";
import featureFlagsPlugin from "./plugins/feature-flags";
const app = createApp(App);
// Install feature flags plugin
app.use(featureFlagsPlugin, {
baseURL: import.meta.env.VITE_FEATURE_FLAGS_BASE_URL,
projectKey: import.meta.env.VITE_FEATURE_FLAGS_PROJECT_KEY,
environmentKey: import.meta.env.VITE_FEATURE_FLAGS_ENVIRONMENT_KEY,
});
app.mount("#app");Usage Examples
Using Flags in Components
Use the composables in any Vue component:
<template>
<header v-if="!isLoading">
<h1>My App</h1>
<nav v-if="enabled">
<a href="/">Home</a>
<a href="/about">About</a>
<p v-if="payload">Variant: {{ payload.variant }}</p>
</nav>
</header>
<div v-else>Loading flags...</div>
</template>
<script setup lang="ts">
import { useFlag } from "@/composables/useFlag";
interface HeaderPayload {
variant: string;
showLogo: boolean;
}
const { enabled, payload, isLoading } = useFlag<HeaderPayload>("header");
</script>Using All Flags
Display all flags in a component:
<template>
<div>
<h2>Feature Flags</h2>
<ul v-if="!isLoading">
<li v-for="flag in flags" :key="flag.slug">
<strong>{{ flag.slug }}</strong>:
{{ flag.enabled ? "enabled" : "disabled" }}
</li>
</ul>
<p v-else>Loading flags...</p>
<button @click="refresh">Refresh Flags</button>
</div>
</template>
<script setup lang="ts">
import { useFlags } from "@/composables/useFlags";
const { flags, isLoading, refresh } = useFlags();
</script>Conditional Rendering
Use flags for conditional rendering:
<template>
<div v-if="enabled">
<NewFeature :config="payload" />
</div>
<div v-else>
<LegacyFeature />
</div>
</template>
<script setup lang="ts">
import { useFlag } from "@/composables/useFlag";
import NewFeature from "./NewFeature.vue";
import LegacyFeature from "./LegacyFeature.vue";
const { enabled, payload } = useFlag<{ theme: string }>("new-feature");
</script>Nuxt 3 Integration
For Nuxt 3, create a plugin file:
import { defineNuxtPlugin } from "#app";
import featureFlagsPlugin from "~/plugins/feature-flags";
export default defineNuxtPlugin((nuxtApp) => {
const config = useRuntimeConfig();
nuxtApp.vueApp.use(featureFlagsPlugin, {
baseURL: config.public.featureFlagsBaseUrl,
projectKey: config.public.featureFlagsProjectKey,
environmentKey: config.public.featureFlagsEnvironmentKey,
});
});Add to nuxt.config.ts:
export default defineNuxtConfig({
runtimeConfig: {
public: {
featureFlagsBaseUrl: process.env.NUXT_PUBLIC_FEATURE_FLAGS_BASE_URL,
featureFlagsProjectKey: process.env.NUXT_PUBLIC_FEATURE_FLAGS_PROJECT_KEY,
featureFlagsEnvironmentKey: process.env.NUXT_PUBLIC_FEATURE_FLAGS_ENVIRONMENT_KEY,
},
},
});Advanced Usage
Preloading Flags
Preload flags on application startup:
import type { App } from "vue";
import { FlagsSDK, type SDKConfig } from "@basestack/flags-js";
export default {
async install(app: App, config: SDKConfig) {
const client = new FlagsSDK(config);
// Preload flags
try {
await client.init();
const { flags } = await client.getAllFlags();
app.provide("flagsClient", client);
app.provide("initialFlags", flags);
} catch (error) {
console.error("Failed to preload flags:", error);
app.provide("flagsClient", client);
app.provide("initialFlags", []);
}
},
};Error Handling
Handle errors gracefully in composables:
<template>
<div v-if="error">
<p>Error loading flag: {{ error.message }}</p>
<p>Using default behavior...</p>
</div>
<div v-else-if="enabled">
<NewFeature />
</div>
<div v-else>
<DefaultFeature />
</div>
</template>
<script setup lang="ts">
import { useFlag } from "@/composables/useFlag";
const { enabled, error } = useFlag("feature");
</script>Best Practices
- Install Plugin Early: Install the plugin in
main.tsbefore mounting the app - Handle Loading States: Always check
isLoadingbefore using flags - Type Payloads: Use TypeScript generics for type-safe payloads
- Error Handling: Provide fallback behavior when flags fail to load
- Preload Flags: Preload flags on app startup for better performance
Troubleshooting
Plugin Not Installed Error
If you see "Feature flags plugin not installed":
Solution: Make sure you've installed the plugin in main.ts:
app.use(featureFlagsPlugin, config);Flags Not Updating
If flags aren't updating:
- Check Cache: Flags are cached for 5 minutes by default
- Call Refresh: Use the
refresh()function from the composable - Verify Environment: Ensure you're using the correct environment key
TypeScript Errors
If you're getting TypeScript errors:
- Type Payloads: Use generics:
useFlag<YourPayloadType>("slug") - Check Imports: Ensure you're importing from the correct paths
- Install Types: Types are included with
@basestack/flags-js
Next Steps
- Explore the JavaScript SDK documentation for more SDK features