A powerful, type-safe feature flag module for Nuxt 3 that enables both static and dynamic feature flag evaluation with server-side support. Perfect for A/B testing, gradual rollouts, and feature management with built-in variant support.
Warning
This project is just getting started, so things are gonna change a lot. Updates will roll out often, and we're totally open to feedback—hit us up with your thoughts!
- 🎯 Context-aware evaluation: Evaluate flags based on request context (user roles, geo-location, device type, etc.)
- 🛠 TypeScript Ready: Full TypeScript support with type-safe flag definitions and autocomplete
- 🧩 Nuxt 3 Integration: Seamless integration with auto-imports and runtime config
- 🎯 Static & Dynamic Flags: Support for both simple boolean flags and dynamic evaluation
- 🔀 A/B/n Testing: Built-in support for feature variants with configurable distribution
- 🎲 Persistent Assignment: Users consistently get the same variant across sessions
- 📊 Validation & Linting: Built-in validation for flag configuration and usage
- 🔒 Type Safety: Catch errors early with full type inference and validation
# Using npx
npx nuxi module add nuxt-feature-flags
# Using npm
npm install nuxt-feature-flags
# Using yarn
yarn add nuxt-feature-flags
# Using pnpm
pnpm add nuxt-feature-flags
- Add the module to your
nuxt.config.ts
:
// Basic usage with plain configuration
export default defineNuxtConfig({
modules: ['nuxt-feature-flags'],
featureFlags: {
flags: {
newDashboard: false,
experimentalFeature: true
}
}
})
// Advanced usage with context-based flag rules
// feature-flags.config.ts
import { defineFeatureFlags } from '#feature-flags/handler'
export default defineFeatureFlags((context) => {
return {
isAdmin: context?.user?.role === 'admin',
newDashboard: true,
experimentalFeature: process.env.NODE_ENV === 'development',
betaFeature: context?.user?.isBetaTester ?? false,
// A/B test with variants
buttonDesign: {
enabled: true,
value: 'default',
variants: [
{ name: 'control', weight: 50, value: 'original' },
{ name: 'treatment', weight: 50, value: 'new-design' }
]
},
// Gradual rollout (30% get new feature)
newCheckout: {
enabled: true,
variants: [
{ name: 'old', weight: 70, value: false },
{ name: 'new', weight: 30, value: true }
]
}
}
})
// nuxt.config.ts
export default defineNuxtConfig({
featureFlags: {
config: './feature-flags.config.ts',
}
})
- Use in your Vue components:
<script setup>
const { isEnabled, getVariant, getValue } = useFeatureFlags()
</script>
<template>
<div>
<!-- Simple feature flag -->
<NewDashboard v-if="isEnabled('newDashboard')" />
<!-- A/B test with variants -->
<div v-feature="'buttonDesign:control'">
<button class="original-style">Click me</button>
</div>
<div v-feature="'buttonDesign:treatment'">
<button class="new-style">Click me</button>
</div>
<!-- Check specific variant programmatically -->
<div v-if="getVariant('buttonDesign') === 'treatment'">
You're seeing the new design! Value: {{ getValue('buttonDesign') }}
</div>
</div>
</template>
- Use in your server routes:
// server/api/dashboard.ts
export default defineEventHandler(async (event) => {
const { isEnabled, getVariant, getValue } = getFeatureFlags(event)
if (!isEnabled('newDashboard')) {
throw createError({
statusCode: 404,
message: 'Dashboard not available'
})
}
// Check if user is in new checkout variant
const checkoutVersion = getVariant('newCheckout')
return {
stats: {
users: 100,
revenue: 50000
},
checkoutVersion,
useNewFeatures: getValue('newCheckout')
}
})
const {
flags, // Reactive flags object
isEnabled, // (flagName: string, variant?: string) => boolean
getVariant, // (flagName: string) => string | undefined
getValue, // (flagName: string) => any
getFlag, // (flagName: string) => ResolvedFlag
} = useFeatureFlags()
// Check if a flag is enabled
if (isEnabled('newFeature')) {
// Feature is enabled
}
// Check specific variant
if (isEnabled('myFlag:variantA')) {
// User is in variant A
}
// Get assigned variant
const variant = getVariant('myFlag') // 'control' | 'treatment' | undefined
// Get flag value
const value = getValue('myFlag') // The resolved value for the user's variant
const {
flags, // Flags object
isEnabled, // (flagName: string, variant?: string) => boolean
getVariant, // (flagName: string) => string | undefined
getValue, // (flagName: string) => any
} = getFeatureFlags(event)
// Same API as client-side
if (isEnabled('newFeature')) {
// Feature is enabled
}
<template>
<!-- Show for all enabled users -->
<div v-feature="'myFlag'">
This shows if myFlag is enabled
</div>
<!-- Show for specific variant -->
<div v-feature="'myFlag:control'">
Control version
</div>
<div v-feature="'myFlag:treatment'">
Treatment version
</div>
</template>
Feature variants allow you to create A/B/n tests and gradual rollouts with consistent user assignment.
// feature-flags.config.ts
import { defineFeatureFlags } from '#feature-flags/handler'
export default defineFeatureFlags(() => {
return {
// Simple A/B test
buttonColor: {
enabled: true,
value: 'blue', // default value
variants: [
{ name: 'blue', weight: 50 },
{ name: 'red', weight: 50, value: 'red' }
]
},
// A/B/C/D test
homepage: {
enabled: true,
variants: [
{ name: 'original', weight: 40, value: 'v1' },
{ name: 'redesign', weight: 30, value: 'v2' },
{ name: 'minimal', weight: 20, value: 'v3' },
{ name: 'experimental', weight: 10, value: 'v4' }
]
},
// Gradual rollout (20% get new feature)
newFeature: {
enabled: true,
variants: [
{ name: 'disabled', weight: 80, value: false },
{ name: 'enabled', weight: 20, value: true }
]
}
}
})
- Persistent: Users get the same variant across sessions (based on user ID, session ID, or IP)
- Weighted Distribution: Variants are assigned based on configured weights (0-100)
- Automatic Normalization: Weights are automatically normalized if they don't sum to 100
<template>
<!-- Different button colors based on variant -->
<button
v-feature="'buttonColor:blue'"
class="bg-blue-500 text-white px-4 py-2"
>
Blue Button (50% of users)
</button>
<button
v-feature="'buttonColor:red'"
class="bg-red-500 text-white px-4 py-2"
>
Red Button (50% of users)
</button>
<!-- Conditional content based on variant -->
<div v-if="getVariant('homepage') === 'redesign'">
<h1>Welcome to our new design!</h1>
</div>
</template>
const { isEnabled, getVariant, getValue } = useFeatureFlags()
// Check if user is in specific variant
if (isEnabled('buttonColor:red')) {
// User sees red button
}
// Get the assigned variant name
const variant = getVariant('buttonColor') // 'blue' | 'red'
// Get the variant value
const color = getValue('buttonColor') // 'blue' | 'red'
// Use in computed properties
const buttonClass = computed(() => {
const color = getValue('buttonColor')
return `bg-${color}-500 text-white px-4 py-2`
})
export default defineNuxtConfig({
featureFlags: {
flags: {
promoBanner: true,
betaFeature: false,
newDashboard: false
}
}
})
// nuxt.config.ts
export default defineNuxtConfig({
featureFlags: {
config: './feature-flags.config.ts',
}
})
// feature-flags.config.ts
import { defineFeatureFlags } from '#feature-flags/handler'
export default defineFeatureFlags(() => ({
isAdmin: false,
newDashboard: true,
experimentalFeature: true,
promoBanner: false,
betaFeature: false,
}))
// feature-flags.config.ts
import { defineFeatureFlags } from '#feature-flags/handler'
export default defineFeatureFlags((context) => {
return {
// User role-based flag
isAdmin: context?.user?.role === 'admin',
// Environment-based flag
devTools: process.env.NODE_ENV === 'development',
// User status-based flag
betaFeature: context?.user?.isBetaTester ?? false,
// Device-based flag
mobileFeature: context?.device?.isMobile ?? false,
}
})
The module includes built-in validation to catch configuration errors and undeclared flag usage.
The module automatically validates:
- Flag naming conventions (alphanumeric, hyphens, underscores)
- Variant weight distribution (0-100, total ≤ 100)
- Duplicate variant names
- Required configuration properties
// build/validate-flags.ts
import { validateFeatureFlags } from 'nuxt-feature-flags/build'
// Validate during build
await validateFeatureFlags({
configPath: './feature-flags.config.ts',
srcPatterns: ['**/*.vue', '**/*.ts'],
failOnErrors: true
})
This checks for:
- Undeclared flags used in code (via
isEnabled('flag')
orv-feature="'flag'"
) - Invalid flag configurations
- Unreferenced flags (declared but never used)
// ❌ Invalid variant weights (total > 100)
badFlag: {
enabled: true,
variants: [
{ name: 'a', weight: 60 },
{ name: 'b', weight: 50 } // Total: 110%
]
}
// ❌ Invalid flag name
'invalid-flag!': true // Contains invalid character
// ❌ Duplicate variant names
duplicateVariants: {
enabled: true,
variants: [
{ name: 'test', weight: 50 },
{ name: 'test', weight: 50 } // Duplicate name
]
}
// ✅ Valid configuration
goodFlag: {
enabled: true,
variants: [
{ name: 'control', weight: 60 },
{ name: 'treatment', weight: 40 } // Total: 100%
]
}
- Clone this repository
- Install dependencies using
npm install
- Start development server using
npm run dev
- Make your changes
- Submit a pull request
Thanks goes to these wonderful people (emoji key):
Roberth González 💻 |
Eugen Istoc 💻 |
Daniel Roe 📖 |
This project follows the all-contributors specification. Contributions of any kind welcome!
MIT License © 2025 Roberth González