Skip to main content

Feature Flags

Control feature availability across tenants and environments.

Overview

Feature flags let you:

  • Enable features for specific tenants
  • Roll out features gradually
  • Toggle features without code deployment
  • A/B test different implementations

Environment-Based Flags

Use NEXT_PUBLIC_* environment variables for client-side feature flags:

# .env.local
NEXT_PUBLIC_ENABLE_CHAT=true
NEXT_PUBLIC_ENABLE_AI=false
NEXT_PUBLIC_ENABLE_ANALYTICS=true

Using Environment Flags

// Check flag in component
function ChatWidget() {
if (process.env.NEXT_PUBLIC_ENABLE_CHAT !== 'true') {
return null;
}

return <ChatInterface />;
}

Server-Side Flags

For server-only features, use non-public variables:

# .env.local
ENABLE_FEATURE_X=true # Server only
// In API route or server component
if (process.env.ENABLE_FEATURE_X === 'true') {
// Feature enabled
}

Tenant-Based Flags

Add feature flags to tenant configuration:

export const myTenantConfig: EAIConfig = {
tenantId: 'my-tenant',
displayName: 'My Application',

features: {
chat: true,
aiAssistant: false,
analytics: true,
experimentalFeatures: false,
},

// ... rest of config
};

Using Tenant Flags

import { useConfig } from '@enterpriseaigroup/client';

function FeatureGate({ feature, children }) {
const config = useConfig();

if (!config.features?.[feature]) {
return null;
}

return children;
}

// Usage
<FeatureGate feature="chat">
<ChatWidget />
</FeatureGate>

Component-Level Flags

Use showWhen for conditional component rendering:

// In tenant config
{
component: 'ExperimentalFeature',
priority: 1,
showWhen: (state) => state.features?.experimental === true,
}

Flag Patterns

Gradual Rollout

Enable for percentage of users:

const enableForPercentage = (percentage: number) => {
const userId = getUserId(); // Get stable user identifier
const hash = hashString(userId);
return (hash % 100) < percentage;
};

// Enable for 10% of users
if (enableForPercentage(10)) {
// Show new feature
}

A/B Testing

features: {
checkoutVariant: 'A', // or 'B'
}

// In component
const variant = config.features?.checkoutVariant;

if (variant === 'A') {
return <CheckoutFlowA />;
} else {
return <CheckoutFlowB />;
}

Feature Dependencies

features: {
baseFeature: true,
advancedFeature: true, // Requires baseFeature
}

// Check both flags
const canUseAdvanced =
config.features?.baseFeature &&
config.features?.advancedFeature;

Runtime Configuration

For dynamic flag changes without redeploy, use the runtime config endpoint:

// Server: /api/eai/config returns
{
features: {
maintenance: true, // Dynamically updated
}
}

// Client
import { useRuntimeConfig } from '@enterpriseaigroup/client';

function App() {
const { features } = useRuntimeConfig();

if (features?.maintenance) {
return <MaintenancePage />;
}

return <MainApp />;
}

Best Practices

1. Name Flags Clearly

// Good
features: {
enableNewCheckout: true,
showBetaFeatures: false,
}

// Avoid
features: {
flag1: true,
newThing: false,
}

2. Default to Off

New features should default to disabled:

const isEnabled = config.features?.newFeature ?? false;

3. Clean Up Old Flags

Remove flags once features are stable:

// TODO: Remove after 2024-06-01
features: {
legacyCheckout: false, // Deprecated
}

4. Document Flag Purpose

features: {
// Enable AI-powered search suggestions
// Owner: @search-team
// Rollout: 50% of enterprise tenants
aiSearch: true,
}

5. Test Both States

Always test with flags both enabled and disabled:

describe('ChatWidget', () => {
test('renders when chat enabled', () => {
// Test with flag on
});

test('returns null when chat disabled', () => {
// Test with flag off
});
});