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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@joemark0008/feature-flags

v1.2.1

Published

Framework-agnostic feature flags library with REST API integration

Readme

@joemark0008/feature-flags

A framework-agnostic TypeScript library for feature flags with REST API integration and React support.

Features

  • 🎯 Framework Agnostic: Core library works with any JavaScript framework
  • ⚛️ React Integration: Built-in hooks and provider for React applications
  • 🌐 REST API Support: Flexible adapter system for any REST API backend
  • 💾 Multiple Storage Options: REST API, localStorage, or in-memory storage
  • 🔄 Caching: Intelligent caching with configurable timeout
  • 🛡️ Type Safe: Full TypeScript support with strong typing
  • 🎚️ Fallback Strategy: Graceful degradation when API is unavailable
  • 📊 Multi-tenant: Organization and user-scoped flag management

Installation

npm install @joemark0008/feature-flags

For React integration, ensure you have React as a peer dependency:

npm install react

🚀 Quick Start with Demo

Want to see it in action first? Try our complete demo:

git clone https://github.com/joemark0008/feature-flag.git
cd feature-flag/demo/feature-flags-demo
npm install
npm run dev

Open http://localhost:5173 to explore all features interactively!

Quick Start

Basic Setup

import { FeatureFlagManager, RestApiAdapter, createRestApiConfig } from '@joemark0008/feature-flags'

// Define your feature flags
const flagConfigs = [
  {
    key: 'dashboardAccess',
    label: 'Dashboard Access',
    description: 'Allow users to access the dashboard',
    category: 'access',
    defaultValue: false,
  },
  {
    key: 'newFeature',
    label: 'New Feature',
    description: 'Enable the new experimental feature',
    category: 'feature',
    defaultValue: false,
  },
]

// Configure REST API
const apiConfig = createRestApiConfig('https://your-api.com', 'your-auth-token')

// Create storage adapter
const storage = new RestApiAdapter(apiConfig)

// Initialize manager
const flagManager = new FeatureFlagManager(storage, flagConfigs, {
  cacheTimeout: 5 * 60 * 1000, // 5 minutes
  enableLocalStorage: true, // Fallback to localStorage
})

// Use in your application
const context = { organizationId: 'org-123', userId: 'user-456' }

const isDashboardEnabled = await flagManager.isEnabled('dashboardAccess', context)
if (isDashboardEnabled) {
  // Show dashboard
}

React Integration

import React from 'react'
import { FeatureFlagProvider, useFeatureFlag } from '@joemark0008/feature-flags/react'

function App() {
  const context = {
    organizationId: user.organizationId,
    userId: user.id,
  }

  return (
    <FeatureFlagProvider 
      manager={flagManager} 
      context={context}
      loadingComponent={LoadingSpinner}
    >
      <Dashboard />
    </FeatureFlagProvider>
  )
}

function Dashboard() {
  const hasDashboardAccess = useFeatureFlag('dashboardAccess')
  const hasNewFeature = useFeatureFlag('newFeature')

  if (!hasDashboardAccess) {
    return <AccessDenied />
  }

  return (
    <div>
      <h1>Dashboard</h1>
      {hasNewFeature && <NewFeatureComponent />}
    </div>
  )
}

API Reference

FeatureFlagManager

The core class for managing feature flags.

class FeatureFlagManager {
  constructor(
    storage: FeatureFlagStorage,
    flagConfigs: FeatureFlagConfig[],
    options?: FeatureFlagManagerOptions
  )

  async isEnabled(flagKey: string, context: FeatureFlagContext): Promise<boolean>
  async getFlags(context: FeatureFlagContext): Promise<Record<string, boolean>>
  async setFlag(flagKey: string, enabled: boolean, context: FeatureFlagContext, scope?: 'organization' | 'user'): Promise<void>
  async setFlags(flags: Record<string, boolean>, context: FeatureFlagContext, scope?: 'organization' | 'user'): Promise<void>
  async getFlagDefinitions(): Promise<FeatureFlagConfig[]>
  clearCache(): void
}

Storage Adapters

RestApiAdapter

import { RestApiAdapter } from '@joemark0008/feature-flags'

const adapter = createFeatureFlagAdapter(process.env.NODE_ENV)

Demo Application

🎯 Try the live demo! A complete React application showcasing all features:

cd demo/feature-flags-demo
npm install
npm run dev

Demo Features:

  • 🔄 Real-time flag toggling with admin panel
  • 🎨 Dynamic theming (light/dark mode)
  • 💾 localStorage persistence
  • 📱 Responsive design with beautiful UI
  • 🔧 Developer tools for debugging
  • 📚 Complete code examples

The demo includes 5 sample feature flags demonstrating:

  • Dashboard redesign toggle
  • Dark mode theme switching
  • Premium features access control
  • Beta chat functionality
  • Advanced analytics display

View Demo Source →

Examples


#### LocalStorageAdapter

```typescript
import { LocalStorageAdapter } from '@joemark0008/feature-flags'

const adapter = new LocalStorageAdapter('my-feature-flags')

MemoryAdapter

Memory Adapter

For testing:

import { MemoryAdapter } from '@joemark0008/feature-flags'

const memoryAdapter = new MemoryAdapter({ newDashboard: true }, flagConfigs)
const manager = new FeatureFlagManager(memoryAdapter, flagConfigs)

GraphQL Adapter

For GraphQL APIs:

import { GraphQLAdapter } from '@joemark0008/feature-flags'

const graphqlAdapter = new GraphQLAdapter({
  endpoint: 'https://your-api.com/graphql',
  headers: { Authorization: 'Bearer token' },
  authInterceptor: (headers) => ({
    ...headers,
    'X-API-Key': process.env.API_KEY
  })
})

const manager = new FeatureFlagManager(graphqlAdapter, flagConfigs)

Using GraphQL Adapter with Existing APIs

The GraphQL adapter is designed to work seamlessly with your existing GraphQL infrastructure. Here are common integration patterns:

1. Custom Queries for Existing Schema

Map the adapter to your existing GraphQL types:

const adapter = new GraphQLAdapter({
  endpoint: 'https://your-existing-api.com/graphql',
  headers: {
    'Authorization': 'Bearer your-token'
  },
  queries: {
    // Map to your existing query structure
    getFlags: `
      query GetFeatureToggles($userId: ID!, $orgId: ID!) {
        user(id: $userId) {
          organization(id: $orgId) {
            featureToggles {
              name
              isActive
              metadata
            }
          }
        }
      }
    `,
    setFlag: `
      mutation UpdateToggle($input: UpdateToggleInput!) {
        updateFeatureToggle(input: $input) {
          id
          success
          toggle {
            name
            isActive
          }
        }
      }
    `
  }
})

2. Integration with Apollo Client

Reuse your existing Apollo Client setup:

import { ApolloClient } from '@apollo/client'

const apolloClient = new ApolloClient({...})

// Use the same endpoint and auth
const featureFlagAdapter = new GraphQLAdapter({
  endpoint: apolloClient.link.options.uri,
  authInterceptor: (headers) => ({
    ...headers,
    // Reuse Apollo's auth logic
    'Authorization': apolloClient.cache.data?.authToken
  }),
  queries: {
    getFlags: `
      query GetFeatureFlags($context: FeatureFlagContextInput!) {
        currentUser {
          featureAccess(context: $context) {
            feature
            hasAccess
          }
        }
      }
    `
  }
})

3. Relay Integration

For Relay-style pagination and connections:

// Use the built-in Relay support
const relayAdapter = GraphQLAdapter.withRelay({
  endpoint: 'https://your-relay-api.com/graphql',
  authInterceptor: (headers) => ({
    ...headers,
    'Authorization': `Bearer ${getAuthToken()}`
  })
})

// Or customize Relay queries
const customRelayAdapter = new GraphQLAdapter({
  endpoint: 'https://api.example.com/graphql',
  queries: {
    getFlags: `
      query GetFeatureFlags($context: FeatureFlagContextInput!, $first: Int, $after: String) {
        viewer {
          featureFlags(context: $context, first: $first, after: $after) {
            edges {
              node {
                key
                enabled
                rolloutPercentage
              }
            }
            pageInfo {
              hasNextPage
              endCursor
            }
          }
        }
      }
    `
  }
})

4. Custom Data Transformation

Handle different response formats by extending the adapter:

class CustomGraphQLAdapter extends GraphQLAdapter {
  async getFlags(context: FeatureFlagContext): Promise<Record<string, boolean>> {
    const operation = {
      query: this.queries.getFlags,
      variables: { context },
    }
    
    const response = await this.executeGraphQL(operation)
    
    // Transform your custom response format
    const flags: Record<string, boolean> = {}
    
    // Example: nested structure transformation
    response.data?.user?.organization?.featureToggles?.forEach(toggle => {
      flags[toggle.name] = toggle.isActive
    })
    
    return flags
  }
}

const adapter = new CustomGraphQLAdapter({
  endpoint: 'https://your-api.com/graphql',
  // ... your config
})

5. Environment-Specific Configuration

Handle different environments with existing GraphQL endpoints:

const createFeatureFlagAdapter = (environment: string) => {
  const configs = {
    development: {
      endpoint: 'http://localhost:4000/graphql',
      authInterceptor: (headers) => ({ 
        ...headers, 
        'x-api-key': 'dev-key' 
      })
    },
    production: {
      endpoint: 'https://api.yourcompany.com/graphql',
      authInterceptor: (headers) => ({ 
        ...headers, 
        'Authorization': `Bearer ${getProductionToken()}` 
      })
    }
  }

  return new GraphQLAdapter({
    ...configs[environment],
    queries: {
      getFlags: `
        query GetFeatureFlags($env: Environment!, $context: UserContext!) {
          featureFlags(environment: $env, context: $context) {
            key
            enabled
            rolloutPercentage
          }
        }
      `
    }
  })
}

const adapter = createFeatureFlagAdapter(process.env.NODE_ENV)

6. Error Handling for Existing APIs

Handle your API's specific error patterns:

const adapter = new GraphQLAdapter({
  endpoint: 'https://your-api.com/graphql',
  authInterceptor: (headers) => {
    const token = localStorage.getItem('authToken')
    return {
      ...headers,
      'Authorization': token ? `Bearer ${token}` : undefined
    }
  },
  queries: {
    getFlags: `
      query GetFlags($context: FeatureFlagContextInput!) {
        featureAccess(input: $context) {
          ... on FeatureAccessSuccess {
            flags {
              name
              enabled
            }
          }
          ... on FeatureAccessError {
            message
            code
          }
        }
      }
    `
  }
})

React Hooks

// Get feature flags context
const { flags, loading, error, refetch } = useFeatureFlags()

// Check individual flag
const isEnabled = useFeatureFlag('flagKey')

// Toggle flags (admin UI)
const toggleFlag = useFeatureFlagToggle()
await toggleFlag('flagKey', true, 'organization')

// Get flags with state
const { flags, loading, error, refetch } = useFeatureFlagsWithState()

Backend Integration

Your REST API should implement these endpoints:

# Get flags for context
GET /api/feature-flags?organizationId=org123&userId=user456
Response: { "dashboardAccess": true, "newFeature": false }

# Set individual flag
PUT /api/feature-flags/dashboardAccess
Body: { "enabled": true, "context": { "organizationId": "org123" }, "scope": "organization" }

PUT /api/feature-flags/:flagKey
Body: { "enabled": true, "context": { "organizationId": "org123" }, "scope": "organization" }

# Bulk update flags
PUT /api/feature-flags
Body: { 
  "flags": { "newDashboard": true, "betaFeatures": false },
  "context": { "organizationId": "org123" },
  "scope": "organization"
}

# Get flag definitions
GET /api/feature-flags/definitions
Response: [{ "key": "newDashboard", "label": "New Dashboard", ... }]

GraphQL API Integration

For GraphQL APIs, your schema should support these operations:

# Schema types
type FeatureFlag {
  key: String!
  enabled: Boolean!
}

type FeatureFlagDefinition {
  key: String!
  label: String!
  description: String!
  category: String!
  defaultValue: Boolean!
  dependencies: [String!]
}

input FeatureFlagContextInput {
  organizationId: String
  userId: String
  environment: String
}

input SetFeatureFlagInput {
  flagKey: String!
  enabled: Boolean!
  context: FeatureFlagContextInput!
  scope: String
}

# Queries
type Query {
  featureFlags(context: FeatureFlagContextInput!): [FeatureFlag!]!
  featureFlagDefinitions: [FeatureFlagDefinition!]!
}

# Mutations
type Mutation {
  setFeatureFlag(input: SetFeatureFlagInput!): SetFeatureFlagResult!
  setFeatureFlags(input: SetFeatureFlagsInput!): SetFeatureFlagsResult!
}

Configuration

Manager Options

interface FeatureFlagManagerOptions {
  cacheTimeout?: number // Cache timeout in ms (default: 5 minutes)
  fallbackToDefaults?: boolean // Use defaults on error (default: true)
  enableLocalStorage?: boolean // Use localStorage fallback (default: true)
  onError?: (error: Error) => void // Error handler
}

Flag Configuration

interface FeatureFlagConfig {
  key: string // Unique identifier
  label: string // Human-readable name
  description: string // Description for admin UI
  category: 'access' | 'feature' | 'experiment' // Flag category
  defaultValue: boolean // Default value when not set
  dependencies?: string[] // Other flags this depends on
}

Context

interface FeatureFlagContext {
  organizationId?: string // Organization scope
  userId?: string // User scope
  userRoles?: string[] // User roles for permission-based flags
  environment?: string // Environment (dev, staging, prod)
  [key: string]: any // Additional context properties
}

Advanced Usage

Custom Authentication

// REST API with custom auth
const apiAdapter = new RestApiAdapter({
  baseUrl: 'https://your-api.com',
  endpoints: { /* ... */ },
  authInterceptor: (config) => ({
    ...config,
    headers: {
      ...config.headers,
      'Authorization': `Bearer ${getAuthToken()}`,
      'X-API-Key': process.env.API_KEY
    }
  })
})

// GraphQL with custom authentication
const graphqlAdapter = new GraphQLAdapter({
  endpoint: 'https://your-api.com/graphql',
  authInterceptor: (headers) => ({
    ...headers,
    'Authorization': `Bearer ${getAuthToken()}`,
    'X-Tenant-ID': getCurrentTenant()
  })
})

Custom GraphQL Queries

const customGraphQLAdapter = GraphQLAdapter.withCustomQueries({
  endpoint: 'https://your-api.com/graphql',
  queries: {
    getFlags: `
      query GetUserFeatureFlags($userId: ID!, $orgId: ID!) {
        user(id: $userId) {
          organization(id: $orgId) {
            featureFlags {
              name
              isEnabled
              rolloutPercentage
            }
          }
        }
      }
    `,
    setFlag: `
      mutation ToggleFeature($input: ToggleFeatureInput!) {
        toggleFeature(input: $input) {
          feature {
            name
            enabled
          }
          errors {
            field
            message
          }
        }
      }
    `
  }
})

// Relay-style pagination support
const relayAdapter = GraphQLAdapter.withRelay({
  endpoint: 'https://your-api.com/graphql',
  headers: { Authorization: 'Bearer token' }
})

Examples

Admin Toggle Component

import { useFeatureFlagToggle, useFeatureFlags } from '@joemark0008/feature-flags/react'

function AdminPanel() {
  const { flags } = useFeatureFlags()
  const toggleFlag = useFeatureFlagToggle()

  const handleToggle = async (flagKey: string) => {
    try {
      await toggleFlag(flagKey, !flags[flagKey])
    } catch (error) {
      console.error('Failed to toggle flag:', error)
    }
  }

  return (
    <div>
      {Object.entries(flags).map(([key, enabled]) => (
        <div key={key}>
          <label>
            <input
              type="checkbox"
              checked={enabled}
              onChange={() => handleToggle(key)}
            />
            {key}
          </label>
        </div>
      ))}
    </div>
  )
}

Custom Storage Adapter

import { FeatureFlagStorage } from '@joemark0008/feature-flags'

class GraphQLAdapter implements FeatureFlagStorage {
  async getFlags(context: FeatureFlagContext): Promise<Record<string, boolean>> {
    // Implement GraphQL query
  }

  async setFlag(flagKey: string, enabled: boolean, context: FeatureFlagContext): Promise<void> {
    // Implement GraphQL mutation
  }

  // ... other methods
}

License

MIT

Contributing

We welcome contributions! Please see our contributing guidelines for more details.