npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

eslint-plugin-feature-flags

v0.1.2

Published

Lint rules to enforce feature flag hygiene: expire old flags, restrict usage, ensure test coverage.

Readme

🚀 ESLint Plugin Feature Flags

Table of Contents

Overview

This ESLint plugin helps you:

  • 🔍 Detect expired feature flags
  • ⚠️ Prevent undefined feature flag usage
  • 🚨 Clean up technical debt

Quick Start

Install

npm install --save-dev eslint-plugin-feature-flags

Configure

// eslint.config.js (ESLint v9+)
import featureFlags from 'eslint-plugin-feature-flags';

const config = {
  'new-feature': {
    expires: '2025-12-31',
    description: 'New feature rollout',
  }
};

export default [
  featureFlags.configs.recommended,
  {
    plugins: { 'feature-flags': featureFlags },
    rules: {
      'feature-flags/expired-feature-flag': ['error', {
        featureFlags: config,
        identifiers: ['isFeatureEnabled']
      }]
    }
  }
];

Use in Your Code

// Define your feature flags
type Features = {
  'new-feature': boolean;
};

const FLAGS: Features = {
  'new-feature': false
};

function isFeatureEnabled(flag: keyof Features): boolean {
  return FLAGS[flag];
}

// ESLint will check this usage
if (isFeatureEnabled('new-feature')) {
  // New code
} else {
  // Old code
}

Available Rules

This plugin provides three powerful rules to help manage feature flags throughout their lifecycle.

1. expired-feature-flag

Purpose: Detects usage of feature flags that have passed their expiration date.

// Configuration
{
  'feature-flags/expired-feature-flag': ['error', {
    featureFlags: {
      'old-feature': {
        expires: '2025-01-01',
        description: 'Deprecated feature'
      }
    },
    identifiers: ['isFeatureEnabled']
  }]
}

Examples:

// ❌ Error: Feature flag "old-feature" expired on 2025-01-01
if (isFeatureEnabled('old-feature')) {
  doSomething();
}

// ✅ OK: Feature not expired
if (isFeatureEnabled('new-feature')) {
  doSomething();
}

2. no-undefined-feature-flags

Purpose: Prevents typos and references to flags not defined in your configuration.

// Configuration
{
  'feature-flags/no-undefined-feature-flags': ['error', {
    featureFlags: {
      'enable-dark-mode': {
        expires: '2025-12-31'
      }
    },
    identifiers: ['isFeatureEnabled']
  }]
}

Examples:

// ❌ Error: Feature flag "unknown-flag" is not defined
if (isFeatureEnabled('unknown-flag')) {
  doSomething();
}

// ✅ OK: Feature is defined in config
if (isFeatureEnabled('enable-dark-mode')) {
  doSomething();
}

3. cleanup-feature-flag

Purpose: Automates the removal of feature flag code with three strategies.

// Configuration
{
  'feature-flags/cleanup-feature-flag': ['warn', {
    identifiers: ['isFeatureEnabled'],
    flagsToCleanup: {
      'legacy-ui': 'preserve-enabled-path',      // Keep the "if" branch
      'enable-beta-feature': 'preserve-disabled-path',  // Keep the "else" branch
      'temp-flag': 'remove-entirely'             // Remove the entire statement
    }
  }]
}

Cleanup Strategies:

  1. preserve-enabled-path: Keeps only the code inside the "if" condition

    // Before:
    if (isFeatureEnabled('legacy-ui')) {
      showNewUI();
    } else {
      showOldUI();
    }
    
    // After:
    showNewUI();
  2. preserve-disabled-path: Keeps only the code inside the "else" condition

    // Before:
    if (isFeatureEnabled('enable-beta-feature')) {
      enableBeta();
    } else {
      useStable();
    }
    
    // After:
    useStable();
  3. remove-entirely: Removes the entire statement

    // Before:
    if (isFeatureEnabled('temp-flag')) {
      doTemporaryThing();
    }
    
    // After:
    // (code removed)

Configuration Examples

ESLint v9+ (Flat Config)

// eslint.config.js
import featureFlags from 'eslint-plugin-feature-flags';

// Define your feature flags
const flags = {
  'ui-v1': {
    expires: '2025-01-01',
    description: 'Legacy UI (deprecated)',
  },
  'ui-v2': {
    expires: '2025-12-31',
    description: 'Current UI version',
  }
};

// Shared options for rules
const options = {
  featureFlags: flags,
  identifiers: ['isFeatureEnabled', 'getFlag'],
};

export default [
  featureFlags.configs.recommended,
  {
    plugins: { 'feature-flags': featureFlags },
    rules: {
      // Detect expired flags
      'feature-flags/expired-feature-flag': ['error', options],
      
      // Prevent undefined flags
      'feature-flags/no-undefined-feature-flags': ['error', options],
      
      // Clean up old flags
      'feature-flags/cleanup-feature-flag': ['warn', {
        ...options,
        flagsToCleanup: {
          'ui-v1': 'preserve-enabled-path'
        }
      }]
    },
  },
];

ESLint v8 and Earlier

// .eslintrc.js
const flags = {
  'ui-v1': {
    expires: '2025-01-01',
    description: 'Legacy UI (deprecated)',
  },
  'ui-v2': {
    expires: '2025-12-31',
    description: 'Current UI version',
  }
};

module.exports = {
  plugins: ['feature-flags'],
  extends: ['plugin:feature-flags/recommended'],
  rules: {
    'feature-flags/expired-feature-flag': ['error', {
      featureFlags: flags,
      identifiers: ['isFeatureEnabled']
    }],
    'feature-flags/no-undefined-feature-flags': ['error', {
      featureFlags: flags,
      identifiers: ['isFeatureEnabled']
    }],
  },
};

Common Setup Pattern

A typical setup follows these steps:

  1. Define your feature flags in a central file
// feature-flags.ts
export const FLAGS = {
  'enable-dark-mode': false,
  'experimental-ui': false,
  'enable-beta-feature': false
};

export type FeatureFlag = keyof typeof FLAGS;

export function isFeatureEnabled(flag: FeatureFlag): boolean {
  return FLAGS[flag];
}
  1. Configure the ESLint plugin
// eslint.config.js
import featureFlags from 'eslint-plugin-feature-flags';

const flagDefinitions = {
  'enable-dark-mode': {
    expires: null, // permanent flag
    description: 'Dark mode theme option'
  },
  'experimental-ui': {
    expires: '2025-12-31',
    description: 'New UI components being tested'
  },
  'enable-beta-feature': {
    expires: '2025-06-30',
    description: 'Beta feature for select users'
  }
};

export default [
  featureFlags.configs.recommended,
  {
    plugins: { 'feature-flags': featureFlags },
    rules: {
      'feature-flags/expired-feature-flag': ['error', {
        featureFlags: flagDefinitions,
        identifiers: ['isFeatureEnabled']
      }],
      'feature-flags/no-undefined-feature-flags': ['error', {
        featureFlags: flagDefinitions,
        identifiers: ['isFeatureEnabled']
      }]
    }
  }
];
  1. Use the flags in your code
import { isFeatureEnabled } from './feature-flags';

function renderButton() {
  if (isFeatureEnabled('experimental-ui')) {
    return <NewButton />;
  }
  return <Button />;
}

Best Practices

  • Set realistic expiration dates - Plan your feature deprecation timeline
  • Document all flags - Add meaningful descriptions for each flag
  • Schedule regular cleanups - Remove flags after they're no longer needed
  • Add to CI pipeline - Catch issues before they reach production
  • Centralize flag management - Keep definitions in one place
  • Use typed flags - Get TypeScript autocomplete and validation

Troubleshooting

  • Plugin not working? - Check that it's correctly installed
  • Rules not triggering? - Ensure function names match your identifiers config
  • Date issues? - Use YYYY-MM-DD format for all dates
  • Config not found? - Check that ESLint is finding your config file