A refreshingly simple approach to feature flags
There are many feature flag services out there. Most of them are large and complex: they rely on APIs, store flags on remote servers, and require sophisticated configurations. Feature Flags Extra Light (ffxl) is different – it's a lightweight, file-based solution for those who need simple feature flags that can be easily stored in your local repository via YAML files. No external services, no complicated setup, just straightforward feature management.
- Lightweight and zero-dependency runtime
- TypeScript support with full type safety
- Works in both browser and Node.js environments
- YAML-based configuration
- Environment variable support for custom config paths
- User-specific feature flags
- Development mode with informative logging
- Comprehensive test coverage
- Modern build tooling with ESM and CJS support
npm install ffxlCreate a feature-flags.yaml file in your project root:
features:
new_dashboard:
enabled: true
comment: "New dashboard UI"
beta_feature:
enabled: false
comment: "Beta feature - not ready yet"
admin_panel:
onlyForUserIds:
- "user-123"
- "user-456"
comment: "Admin panel - restricted access"import { loadFeatureFlags } from 'ffxl';
// During your build process or server initialization
const config = loadFeatureFlags();
// Set as environment variable for runtime access
process.env.FFXL_CONFIG = JSON.stringify(config);import { isFeatureEnabled } from 'ffxl';
// Simple check
if (isFeatureEnabled('new_dashboard')) {
// Show new dashboard
}
// With user context
const user = { userId: 'user-123' };
if (isFeatureEnabled('admin_panel', user)) {
// Show admin panel
}Loads and validates feature flags from YAML file. Only works in Node.js environment.
import { loadFeatureFlags } from 'ffxl';
const config = loadFeatureFlags();Loads feature flags and returns as JSON string. Useful for build tools.
import { loadFeatureFlagsAsString } from 'ffxl';
const configString = loadFeatureFlagsAsString();
process.env.FFXL_CONFIG = configString;Checks if a feature is enabled for the given user.
import { isFeatureEnabled } from 'ffxl';
// Global feature
isFeatureEnabled('new_dashboard'); // true/false
// User-specific feature
isFeatureEnabled('admin_panel', { userId: 'user-123' }); // true/falseReturns true if ANY of the specified features are enabled.
import { isAnyFeatureEnabled } from 'ffxl';
isAnyFeatureEnabled(['feature1', 'feature2'], user);Returns true if ALL of the specified features are enabled.
import { areAllFeaturesEnabled } from 'ffxl';
areAllFeaturesEnabled(['feature1', 'feature2'], user);Gets all enabled features for the given user.
import { getEnabledFeatures } from 'ffxl';
const enabled = getEnabledFeatures(user);
// ['new_dashboard', 'admin_panel']Gets multiple feature flags at once as an object.
import { getFeatureFlags } from 'ffxl';
const flags = getFeatureFlags(['feature1', 'feature2'], user);
// { feature1: true, feature2: false }Checks if a feature exists in the configuration.
Gets all feature names from the configuration.
Gets the raw configuration for a specific feature. Useful for debugging.
By default, ffxl looks for feature-flags.yaml in your project root. You can customize this using environment variables:
# Option 1: Use FEATURE_FLAGS_FILE
FEATURE_FLAGS_FILE=./config/flags.yaml
# Option 2: Use FFXL_FILE
FFXL_FILE=./config/flags.yamlfeatures:
feature_name:
enabled: true # Global enable/disable
comment: "Description" # Optional comment
user_specific_feature:
onlyForUserIds: # User-specific access
- "user-id-1"
- "user-id-2"
comment: "Description"
combined_feature:
enabled: false # User list takes precedence
onlyForUserIds:
- "test-user-id"- Each feature must have either
enabled(boolean) oronlyForUserIds(array) - When
onlyForUserIdsis present and non-empty, it takes precedence overenabled - User-specific features require a user object with
userIdproperty - Unknown features default to disabled with a development warning
interface UserIdentity {
userId?: string; // Primary identifier
handle?: string; // Optional username
email?: string; // Optional email
}// In your build script or server initialization
import { loadFeatureFlags } from 'ffxl';
const config = loadFeatureFlags();
process.env.FFXL_CONFIG = JSON.stringify(config);
// In your application code
import { isFeatureEnabled } from 'ffxl';
app.get('/api/dashboard', (req, res) => {
if (isFeatureEnabled('new_dashboard', req.user)) {
// Serve new dashboard
} else {
// Serve old dashboard
}
});Ensure your build tool sets the FFXL_CONFIG environment variable:
// In your Vite/Webpack config
import { loadFeatureFlagsAsString } from 'ffxl';
export default {
define: {
'process.env.FFXL_CONFIG': loadFeatureFlagsAsString()
}
}
// In your application
import { isFeatureEnabled } from 'ffxl';
function Dashboard({ user }) {
const showNewUI = isFeatureEnabled('new_dashboard', user);
return showNewUI ? <NewDashboard /> : <OldDashboard />;
}// next.config.ts
import { loadFeatureFlags } from 'ffxl';
const featureFlags = loadFeatureFlags();
export default {
env: {
FFXL_CONFIG: JSON.stringify(featureFlags)
}
}
// In your components or API routes
import { isFeatureEnabled } from 'ffxl';
export default function Page() {
if (isFeatureEnabled('new_feature')) {
return <NewFeature />;
}
return <OldFeature />;
}In development mode (NODE_ENV=development or NODE_ENV=dev), ffxl provides helpful logging:
- Configuration load success/failure
- Unknown feature warnings
- Evaluation errors
Logs output to:
- Browser:
console(info, warn, error) - Node.js:
process.stdoutandprocess.stderr
ffxl is written in TypeScript and provides full type definitions:
import type {
FeatureFlagConfig,
FeatureFlagsConfig,
UserIdentity,
Environment
} from 'ffxl';# Run tests
npm test
# Watch mode
npm run test:watch
# Coverage
npm run test:coverageContributions are welcome! Please feel free to submit a Pull Request.
Drop me a line if uo have any questions or suggestions:
LinkedIn • X/Twitter • Website • [email protected]
MIT