Basestack Docs
Feature flagsSDKsFrontend

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:

  1. FeatureFlags Plugin: A Vue plugin that provides the SDK instance globally
  2. useFlag Composable: A composable to fetch and use individual flags reactively
  3. useFlags Composable: A composable to fetch and use all flags reactively
  4. 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.

Install the SDK
npm install @basestack/flags-js
pnpm install @basestack/flags-js
yarn add @basestack/flags-js
bun add @basestack/flags-js

Environment 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_.

.env
# 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:

useFlag.ts
useFlags.ts
feature-flags.ts
main.ts
.env

File Structure:

  • plugins/feature-flags.ts - Vue plugin for global SDK instance
  • composables/useFlag.ts - Composable for individual flags
  • composables/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:

src/plugins/feature-flags.ts
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/inject system
  • 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:

src/composables/useFlag.ts
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:

src/composables/useFlags.ts
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:

src/main.ts
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:

src/components/Header.vue
<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:

src/components/FlagsList.vue
<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:

src/components/Feature.vue
<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:

plugins/feature-flags.client.ts
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:

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:

src/plugins/feature-flags.ts
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:

src/components/SafeFeature.vue
<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

  1. Install Plugin Early: Install the plugin in main.ts before mounting the app
  2. Handle Loading States: Always check isLoading before using flags
  3. Type Payloads: Use TypeScript generics for type-safe payloads
  4. Error Handling: Provide fallback behavior when flags fail to load
  5. 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:

  1. Check Cache: Flags are cached for 5 minutes by default
  2. Call Refresh: Use the refresh() function from the composable
  3. Verify Environment: Ensure you're using the correct environment key

TypeScript Errors

If you're getting TypeScript errors:

  1. Type Payloads: Use generics: useFlag<YourPayloadType>("slug")
  2. Check Imports: Ensure you're importing from the correct paths
  3. Install Types: Types are included with @basestack/flags-js

Next Steps