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

flipflag-js-sdk

v1.0.0

Published

Lightweight TypeScript SDK for FlipFlag feature flags service. Works in browser and Node.js environments.

Readme

FlipFlag JavaScript SDK

npm version TypeScript License: MIT

Lightweight TypeScript SDK for FlipFlag feature flags service. Works seamlessly in both browser and Node.js environments with zero external dependencies.

🚀 Features

  • 🌐 Universal: Works in both browser and Node.js environments
  • ⚡ Lightweight: No external dependencies, only uses native fetch
  • 🔄 Auto-updating: Configurable polling for real-time flag updates
  • 💾 Smart Caching: Uses localStorage (browser) or in-memory cache (Node.js)
  • 📢 Event-driven: Subscribe to flag changes and updates
  • 🛡️ Error Resilient: Graceful fallback to cached values when server is unavailable
  • 📝 TypeScript: Written in TypeScript with full type definitions
  • ⚛️ React Ready: Global hook system for React apps without prop drilling

📦 Installation

npm install flipflag-js-sdk

For Node.js versions < 18, you'll also need to install node-fetch:

npm install node-fetch

🏁 Quick Start

React (Recommended for React apps)

// 1. Initialize once in your app root
import React, { useEffect } from "react";
import { initFlipFlag } from "flipflag-js-sdk/react";

function App() {
  useEffect(() => {
    initFlipFlag({
      apiKey: "ff_your_api_key_here",
      environment: "prod",
    });
  }, []);

  return (
    <div>
      <Dashboard />
    </div>
  );
}

// 2. Use in any component without prop drilling
import { useFlipFlag } from "flipflag-js-sdk/react";

function Dashboard() {
  const { isEnabled } = useFlipFlag();

  return (
    <div>
      {isEnabled("new-feature") && <NewFeature />}
      {isEnabled("dark-mode") ? <DarkTheme /> : <LightTheme />}
    </div>
  );
}

Browser

<!DOCTYPE html>
<html>
  <head>
    <script src="https://unpkg.com/flipflag-js-sdk@latest/dist/flipflag-sdk.js"></script>
  </head>
  <body>
    <script>
      const flipflag = new FlipFlagSDK();

      flipflag
        .init({
          apiKey: "ff_your_api_key_here",
          environment: "prod",
        })
        .then(() => {
          console.log("SDK initialized!");

          if (flipflag.isEnabled("dark-mode")) {
            document.body.classList.add("dark-theme");
          }

          if (flipflag.isEnabled("new-dashboard")) {
            loadNewDashboard();
          } else {
            loadLegacyDashboard();
          }
        });
    </script>
  </body>
</html>

Node.js

import FlipFlagSDK from "flipflag-js-sdk";

async function main() {
  const flipflag = new FlipFlagSDK();

  await flipflag.init({
    apiKey: "ff_your_api_key_here",
    baseUrl: "https://api.flipflag.dev",
    environment: "prod",
    pollInterval: 60000, // Poll every 60 seconds
  });

  // Check feature flags
  if (flipflag.isEnabled("new-feature")) {
    console.log("New feature is enabled!");
    // Implement new feature logic
  }

  if (flipflag.isEnabled("analytics-tracking")) {
    // Initialize analytics
    analytics.init();
  }

  // Subscribe to flag updates
  flipflag.on("update", (data) => {
    console.log("Flags updated:", data.changedFlags);
  });
}

main().catch(console.error);

📖 API Reference

Constructor

const flipflag = new FlipFlagSDK();

Methods

init(config)

Initialize the SDK with your configuration.

Parameters:

  • config.apiKey (string, required): Your FlipFlag API key
  • config.projectId (string, optional): Project ID for better performance
  • config.baseUrl (string, optional): API base URL (default: http://localhost:3002)
  • config.environment (string, optional): Environment (dev, staging, prod) (default: prod)
  • config.pollInterval (number, optional): Polling interval in milliseconds (default: 60000)

Returns: Promise<void>

await flipflag.init({
  apiKey: "ff_your_api_key_here",
  baseUrl: "https://api.flipflag.dev",
  environment: "prod",
  pollInterval: 30000,
});

isEnabled(flagName, defaultValue?)

Check if a feature flag is enabled.

Parameters:

  • flagName (string): Name of the feature flag
  • defaultValue (boolean, optional): Default value if flag is not found (default: false)

Returns: boolean

const isEnabled = flipflag.isEnabled("dark-mode");
const isEnabledWithDefault = flipflag.isEnabled("new-feature", true);

getAllFlags()

Get all flags as an object.

Returns: Record<string, boolean> - All flags as key-value pairs

const allFlags = flipflag.getAllFlags();
console.log(allFlags); // { "dark-mode": true, "new-dashboard": false }

refresh()

Manually refresh flags from the server.

Returns: Promise<void>

await flipflag.refresh();

on(event, callback)

Subscribe to events.

Events:

  • 'initialized': Fired when SDK is initialized
  • 'update': Fired when flags are updated
  • 'error': Fired when an error occurs
flipflag.on("update", (data) => {
  console.log("Changed flags:", data.changedFlags);
  console.log("All flags:", data.flags);
});

flipflag.on("error", (error) => {
  console.error("SDK Error:", error.error);
});

off(event, callback)

Unsubscribe from events.

const updateHandler = (data) => console.log("Updated:", data);
flipflag.on("update", updateHandler);
flipflag.off("update", updateHandler);

destroy()

Stop polling and cleanup resources.

flipflag.destroy();

⚛️ React Integration

Global Hook Approach

1. Initialize in App Root

// App.tsx
import React, { useEffect } from "react";
import { initFlipFlag } from "flipflag-js-sdk/react";

const App: React.FC = () => {
  useEffect(() => {
    initFlipFlag({
      apiKey: process.env.REACT_APP_FLIPFLAG_API_KEY!,
      projectId: process.env.REACT_APP_FLIPFLAG_PROJECT_ID,
      environment: process.env.NODE_ENV === "production" ? "prod" : "dev",
      baseUrl: process.env.REACT_APP_FLIPFLAG_BASE_URL,
    }).catch(console.error);
  }, []);

  return (
    <div className="App">
      <Dashboard />
    </div>
  );
};

export default App;

2. Use Hook in Any Component

// components/Dashboard.tsx
import React from "react";
import { useFlipFlag } from "flipflag-js-sdk/react";

const Dashboard: React.FC = () => {
  const { isEnabled, flags, isInitialized } = useFlipFlag();

  if (!isInitialized) {
    return <div>Loading feature flags...</div>;
  }

  return (
    <div className={isEnabled("dark-mode") ? "dark-theme" : "light-theme"}>
      <h1>Dashboard</h1>

      {isEnabled("new-dashboard") ? <NewDashboard /> : <LegacyDashboard />}

      {isEnabled("analytics-tracking") && <AnalyticsComponent />}

      {isEnabled("beta-features") && (
        <div className="beta-section">
          <h2>Beta Features</h2>
          <BetaFeaturesList />
        </div>
      )}
    </div>
  );
};

export default Dashboard;

3. Use Anywhere Without Prop Drilling

// components/Header.tsx
import React from "react";
import { useFlipFlag } from "flipflag-js-sdk/react";

const Header: React.FC = () => {
  const { isEnabled } = useFlipFlag();

  return (
    <header>
      <h1>{isEnabled("new-branding") ? "FlipFlag Pro" : "FlipFlag"}</h1>
      {isEnabled("search-feature") && <SearchBar />}
      {isEnabled("notifications") && <NotificationBell />}
    </header>
  );
};

export default Header;

Declarative Component Approach

import React from "react";
import { FeatureFlag } from "flipflag-js-sdk/react";

const ProductPage: React.FC = () => {
  return (
    <div>
      <h1>Product Page</h1>

      <FeatureFlag flag="new-product-gallery">
        <NewProductGallery />
      </FeatureFlag>

      <FeatureFlag
        flag="reviews-section"
        fallback={<div>Reviews coming soon!</div>}
      >
        <ReviewsSection />
      </FeatureFlag>

      <FeatureFlag flag="chat-support">
        <ChatWidget />
      </FeatureFlag>
    </div>
  );
};

Higher-Order Component Approach

import React from "react";
import { withFeatureFlag } from "flipflag-js-sdk/react";

// Only render component if flag is enabled
const BetaFeatureComponent: React.FC = () => <div>This is a beta feature!</div>;

const BetaFeature = withFeatureFlag("beta-features")(BetaFeatureComponent);

// Usage
const App: React.FC = () => (
  <div>
    <h1>My App</h1>
    <BetaFeature /> {/* Only renders if 'beta-features' flag is true */}
  </div>
);

Outside React Components

// utils/analytics.ts
import { isEnabled } from "flipflag-js-sdk/react";

export const trackEvent = (eventName: string, data: any) => {
  if (isEnabled("analytics-tracking")) {
    // Send analytics event
    analytics.track(eventName, data);
  }
};

export const getAnalyticsConfig = () => {
  return {
    enableAdvancedTracking: isEnabled("advanced-analytics"),
    enableHeatmaps: isEnabled("heatmap-tracking"),
    samplingRate: isEnabled("high-volume-analytics") ? 1.0 : 0.1,
  };
};

🌐 Next.js Integration

App Router (Next.js 13+)

// app/components/flipflag-initializer.tsx
"use client";

import { useEffect } from "react";
import { initFlipFlag } from "flipflag-js-sdk/react";

export default function FlipFlagInitializer() {
  useEffect(() => {
    initFlipFlag({
      apiKey: process.env.NEXT_PUBLIC_FLIPFLAG_API_KEY!,
      projectId: process.env.NEXT_PUBLIC_FLIPFLAG_PROJECT_ID,
      environment: process.env.NODE_ENV === "production" ? "prod" : "dev",
      baseUrl:
        process.env.NEXT_PUBLIC_FLIPFLAG_BASE_URL || "http://localhost:3002",
      pollInterval: 60000,
    }).catch(console.error);
  }, []);

  return null; // This component doesn't render anything
}
// app/layout.tsx
import FlipFlagInitializer from "./components/flipflag-initializer";
import "./globals.css";

export const metadata = {
  title: "My Next.js App with Feature Flags",
  description: "App using FlipFlag for feature management",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <FlipFlagInitializer />
        {children}
      </body>
    </html>
  );
}
// app/components/dashboard.tsx
"use client";

import { useFlipFlag } from "flipflag-js-sdk/react";

export default function Dashboard() {
  const { isEnabled, flags, isInitialized } = useFlipFlag();

  if (!isInitialized) {
    return (
      <div className="flex items-center justify-center min-h-screen">
        <div className="text-center">
          <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto"></div>
          <p className="mt-4 text-gray-600">Loading feature flags...</p>
        </div>
      </div>
    );
  }

  return (
    <div className={isEnabled("dark-mode") ? "dark" : ""}>
      <div className="min-h-screen bg-white dark:bg-gray-900 text-black dark:text-white">
        <header className="p-6 border-b border-gray-200 dark:border-gray-700">
          <h1 className="text-3xl font-bold">
            {isEnabled("new-branding") ? "FlipFlag Pro Dashboard" : "Dashboard"}
          </h1>
        </header>

        <main className="p-6">
          {isEnabled("new-dashboard-layout") ? (
            <NewDashboardLayout />
          ) : (
            <LegacyDashboardLayout />
          )}

          {isEnabled("analytics-widget") && (
            <div className="mt-6">
              <AnalyticsWidget />
            </div>
          )}

          {isEnabled("beta-features") && (
            <div className="mt-6 p-4 bg-yellow-100 dark:bg-yellow-900 rounded-lg">
              <h2 className="text-lg font-semibold mb-2">🚀 Beta Features</h2>
              <BetaFeaturesPanel />
            </div>
          )}
        </main>
      </div>
    </div>
  );
}

Pages Router (Next.js 12 and below)

// pages/_app.tsx
import { useEffect } from "react";
import { initFlipFlag } from "flipflag-js-sdk/react";
import "../styles/globals.css";
import { AppProps } from "next/app";

function MyApp({ Component, pageProps }: AppProps) {
  useEffect(() => {
    initFlipFlag({
      apiKey: process.env.NEXT_PUBLIC_FLIPFLAG_API_KEY!,
      projectId: process.env.NEXT_PUBLIC_FLIPFLAG_PROJECT_ID,
      environment: process.env.NODE_ENV === "production" ? "prod" : "dev",
      baseUrl: process.env.NEXT_PUBLIC_FLIPFLAG_BASE_URL,
    }).catch(console.error);
  }, []);

  return <Component {...pageProps} />;
}

export default MyApp;
// pages/index.tsx
import { useFlipFlag } from "flipflag-js-sdk/react";
import { useEffect, useState } from "react";

export default function Home() {
  const { isEnabled, isInitialized } = useFlipFlag();
  const [mounted, setMounted] = useState(false);

  // Prevent hydration mismatch
  useEffect(() => {
    setMounted(true);
  }, []);

  if (!mounted || !isInitialized) {
    return <div>Loading...</div>;
  }

  return (
    <div className={isEnabled("dark-mode") ? "dark" : ""}>
      <main className="min-h-screen bg-white dark:bg-gray-900">
        <h1 className="text-4xl font-bold p-8">
          Welcome to Next.js with FlipFlag!
        </h1>

        {isEnabled("hero-banner") && (
          <section className="bg-blue-500 text-white p-8 m-4 rounded">
            <h2 className="text-2xl font-bold">🚀 New Hero Banner!</h2>
            <p>This banner is controlled by the 'hero-banner' feature flag.</p>
          </section>
        )}

        {isEnabled("pricing-section") && <PricingSection />}
        {isEnabled("testimonials") && <TestimonialsSection />}
      </main>
    </div>
  );
}

Server-Side Feature Flags with API Routes

// pages/api/dashboard-config.ts
import { NextApiRequest, NextApiResponse } from "next";
import FlipFlagSDK from "flipflag-js-sdk";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  // Initialize FlipFlag SDK on server-side
  const flipflag = new FlipFlagSDK();

  try {
    await flipflag.init({
      apiKey: process.env.FLIPFLAG_API_KEY!, // Server-side env var
      projectId: process.env.FLIPFLAG_PROJECT_ID,
      environment: process.env.NODE_ENV === "production" ? "prod" : "dev",
      baseUrl: process.env.FLIPFLAG_BASE_URL,
      pollInterval: 0, // Disable polling on server
    });

    const config = {
      showPremiumFeatures: flipflag.isEnabled("premium-features"),
      enableNewAPI: flipflag.isEnabled("new-api-format"),
      maintenanceMode: flipflag.isEnabled("maintenance-mode"),
      maxUsers: flipflag.isEnabled("increased-limits") ? 10000 : 1000,
    };

    flipflag.destroy();

    res.status(200).json(config);
  } catch (error) {
    console.error("Failed to fetch feature flags:", error);

    // Return default configuration
    res.status(200).json({
      showPremiumFeatures: false,
      enableNewAPI: false,
      maintenanceMode: false,
      maxUsers: 1000,
    });
  }
}

🔧 Configuration

Environment Variables

# React/Next.js Client-side
REACT_APP_FLIPFLAG_API_KEY=ff_your_client_api_key
REACT_APP_FLIPFLAG_PROJECT_ID=your-project-id
REACT_APP_FLIPFLAG_BASE_URL=https://api.flipflag.dev

# Next.js
NEXT_PUBLIC_FLIPFLAG_API_KEY=ff_your_client_api_key
NEXT_PUBLIC_FLIPFLAG_PROJECT_ID=your-project-id
NEXT_PUBLIC_FLIPFLAG_BASE_URL=https://api.flipflag.dev

# Server-side only (for API routes)
FLIPFLAG_API_KEY=ff_your_server_api_key
FLIPFLAG_PROJECT_ID=your-project-id
FLIPFLAG_BASE_URL=https://api.flipflag.dev

Environment-specific Configuration

// Development
await flipflag.init({
  apiKey: "ff_dev_key",
  environment: "dev",
  pollInterval: 10000, // More frequent polling in dev
});

// Production
await flipflag.init({
  apiKey: "ff_prod_key",
  environment: "prod",
  pollInterval: 300000, // Less frequent polling in prod
});

Custom Polling Strategy

// Disable automatic polling and use manual refresh
await flipflag.init({
  apiKey: "ff_your_api_key",
  pollInterval: 0, // Disable polling
});

// Manually refresh when needed
setInterval(async () => {
  try {
    await flipflag.refresh();
  } catch (error) {
    console.warn("Manual refresh failed:", error);
  }
}, 120000); // Every 2 minutes

💾 Caching

The SDK automatically caches flag values to ensure your application continues to work even when the FlipFlag service is temporarily unavailable.

Browser

  • Uses localStorage to persist flags across browser sessions
  • Automatically falls back to cached values on network errors

Node.js

  • Uses in-memory caching
  • Cache is lost when the process restarts

Cache Key Format

flipflag_{apiKey}_{environment}

🔄 Polling and Updates

The SDK automatically polls for flag updates at the specified interval. When flags change, the SDK emits update events.

// Set up flag change handlers
flipflag.on("update", (data) => {
  // Handle any flag changes
  Object.keys(data.changedFlags).forEach((flagName) => {
    console.log(`Flag ${flagName} changed to: ${data.changedFlags[flagName]}`);
  });
});

🛡️ Error Handling

The SDK is designed to be resilient to network issues and API errors.

flipflag.on("error", (error) => {
  switch (error.type) {
    case "initialization":
      console.error("Failed to initialize:", error.error);
      break;
    case "polling":
      console.warn("Polling failed, using cached data:", error.error);
      break;
    case "network":
      console.warn("Network error:", error.error);
      break;
    case "authentication":
      console.error("Authentication error:", error.error);
      break;
  }
});

try {
  await flipflag.init({ apiKey: "invalid_key" });
} catch (error) {
  console.error("Initialization failed:", error.message);
  // App can still function with default values
  const isFeatureEnabled = flipflag.isEnabled("my-feature", false);
}

🧪 Testing

When testing your application, you can mock the FlipFlag SDK:

// Mock for Jest
jest.mock("flipflag-js-sdk", () => ({
  __esModule: true,
  default: class MockFlipFlagSDK {
    private flags = new Map<string, boolean>();

    async init() {}

    isEnabled(flagName: string, defaultValue: boolean = false): boolean {
      return this.flags.get(flagName) ?? defaultValue;
    }

    // Test helper to set flag values
    setFlag(flagName: string, value: boolean) {
      this.flags.set(flagName, value);
    }

    on() {}
    off() {}
    destroy() {}
  },
}));

// In your test
import FlipFlagSDK from "flipflag-js-sdk";
const flipflag = new FlipFlagSDK();
(flipflag as any).setFlag("new-feature", true);
expect(flipflag.isEnabled("new-feature")).toBe(true);

🚨 Error Codes

| Error | Description | Solution | | ------------------------------------ | ----------------------------------------- | -------------------------------------------- | | API key is required | No API key provided | Provide a valid API key in init() | | Invalid API key format | API key doesn't start with ff_ | Use a valid FlipFlag API key | | Invalid API key | API key is not valid | Check your API key in FlipFlag dashboard | | API key not valid for this project | API key belongs to different project | Use the correct API key for your project | | Too many requests | Rate limit exceeded | Reduce polling frequency or retry later | | Project not found | Project associated with API key not found | Verify project exists and API key is correct |

📄 License

MIT License - see LICENSE file for details.

🤝 Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📞 Support


Made with ❤️ by the FlipFlag team