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

featurely-site-manager

v1.4.1

Published

Complete site management SDK for maintenance mode, status messages, feature flags, version checking, and analytics

Readme

featurely-site-manager

Comprehensive site management SDK. Handles maintenance mode, status message banners, feature flags (including A/B testing and percentage rollouts), app version checking, and analytics — all controlled from the Featurely dashboard with real-time polling.

Installation

npm install featurely-site-manager

Required API key permission: public:read

Quick Start

import { SiteManager } from "featurely-site-manager";

const manager = new SiteManager({
  apiKey: "ft_live_your_api_key_here",
  projectId: "your-project-id",
});
await manager.init();

if (manager.isFeatureEnabled("new-checkout")) {
  renderNewCheckout();
}

Configuration

SiteManagerConfig

| Option | Type | Default | Description | | --------------------------- | --------------------------------------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | apiKey | string | required | API key with public:read permission | | projectId | string | required | Your Featurely project ID | | apiUrl | string | "https://www.featurely.no" | Custom API base URL | | environment | string | — | Hostname override or explicit environment slug (e.g. process.env.NEXT_PUBLIC_ENV). Use server-side where window is unavailable | | pollInterval | number | 60000 | Config refresh interval in ms | | userEmail | string | — | User email for maintenance whitelist checks | | userId | string | — | User ID for flag bucketing and analytics | | customAttributes | Record<string, string \| number \| boolean> | — | Custom targeting attributes for feature flag rules (e.g. { plan: "pro" }) | | bootstrapFlags | Record<string, boolean> | — | Pre-seed flag values from the server (SSR) to avoid flash on initial render | | bypassCheck | () => boolean | — | Custom maintenance bypass logic | | enableAnalytics | boolean | true | Track page views, web vitals, and custom events | | analyticsFlushInterval | number | 60000 | Analytics send interval in ms | | appVersion | string | — | Current app version for version checking | | enableVersionCheck | boolean | false | Poll for app version updates | | versionCheckInterval | number | 3600000 | Version check interval in ms | | platform | string | — | Platform identifier for version-check endpoint ("web" \| "ios" \| "android" \| "electron" \| "tauri") | | updateRules | VersionUpdateRules | — | Override server classification per semver change type | | autoInjectBanners | boolean | true | Inject status message banners directly into the DOM. Set false for custom banner UI | | autoCaptureClicks | boolean | false | Track clicks on elements with data-featurely-click attribute | | enableHeatmaps | boolean | false | Track click coordinates for heatmap visualization | | enableRageClickDetection | boolean | false | Detect frustration patterns (5+ clicks in same area) | | enableScrollTracking | boolean | false | Track scroll depth at 25%, 50%, 75%, 90%, 100% milestones | | enablePerformanceTracking | boolean | false | Monitor resource timing and long tasks | | heatmapSampleRate | number | 10 | Percentage of clicks to sample for heatmaps (0-100) | | debugMode | boolean | false | Show floating debug overlay | | debugSecret | string | — | Enable overlay in production via ?ft_debug=<secret> URL param | | onMaintenanceEnabled | (config: MaintenanceConfig) => void | — | Called when maintenance mode activates | | onMaintenanceDisabled | () => void | — | Called when maintenance mode deactivates | | onMessageReceived | (message: StatusMessage) => void | — | Called when a banner is shown (fires even with auto-injection) | | onMessageDismissed | (id: string) => void | — | Called when a banner is dismissed | | onFeatureFlagsUpdated | (flags: FeatureFlag[]) => void | — | Called when flag config changes | | onUpdateAvailable | (info: VersionCheckResponse) => void | — | Called when a newer app version is available | | onUpdateRequired | (info: VersionCheckResponse) => void | — | Called when an update is mandatory | | onError | (error: Error) => void | — | Called on SDK-internal errors |

VersionUpdateRules

| Option | Type | Default | | ------- | -------------------------------------------- | --------------- | | major | "required" \| "available" | "required" | | minor | "required" \| "available" | "available" | | patch | "required" \| "recommended" \| "available" | "recommended" |

Methods

manager.init(): Promise<void>

Fetch config, start polling, start analytics, and inject styles. Must be awaited before reading flags or maintenance state.

manager.destroy(): void

Stop polling, flush pending analytics, and clean up the DOM overlay.

manager.setUser(email, userId?, customAttributes?): void

Update user identity. Custom attributes are merged into any existing attributes. Resets flag bucketing so targeting rules re-evaluate.

manager.isFeatureEnabled(flagKey, defaultValue?): boolean

Returns true if the flag is enabled for the current user. Returns defaultValue (default: false) while config is loading. Local overrides and URL params (?ft_<key>=true) take precedence.

manager.getFeatureVariant(flagKey): string | null

Returns the A/B variant key assigned to the current user, or null if the flag is not an A/B test or the user is not in the experiment.

manager.getAllFeatureFlags(): FeatureFlag[]

Returns all feature flags (enabled and disabled).

manager.getEnabledFeatures(): string[]

Returns the keys of all flags that are currently enabled for the current user.

manager.overrideFlag(flagKey, value): void

Set a local flag override for testing. Pass null to remove the override. Overrides take precedence over server-fetched values.

manager.isInMaintenanceMode(): boolean

Returns true if maintenance mode is active for the current user (bypass not applicable).

manager.getActiveMessages(): StatusMessage[]

Returns all status messages currently active for the current page and time. Dismissed messages and messages outside their startsAt/expiresAt window are excluded.

manager.getActiveEnvironment(): { id, name, slug } | null

Returns the matched environment for the current hostname or environment config value.

manager.isErrorLoggingEnabled(): boolean

Returns true if error logging is enabled for the matched environment. Defaults to true when no environment matches.

manager.trackEvent(eventName, properties?): void

Queue a custom analytics event. Events are batched and sent on analyticsFlushInterval.

manager.trackRevenue(eventName, amount, currency, properties?): void

Track revenue events (purchases, subscriptions, etc.). Amount should be in smallest currency unit (cents).

Example: manager.trackRevenue('purchase', 4999, 'USD', { productId: 'pro-plan' })

manager.refresh(): Promise<void>

Immediately trigger a config poll without waiting for the next interval.

Examples

Feature flags

import { SiteManager } from "featurely-site-manager";

const manager = new SiteManager({
  apiKey: process.env.NEXT_PUBLIC_FEATURELY_API_KEY!,
  projectId: process.env.NEXT_PUBLIC_PROJECT_ID!,
  userId: user.id,
  customAttributes: { plan: user.plan },
});
await manager.init();

if (manager.isFeatureEnabled("new-checkout")) {
  renderNewCheckout();
}

// A/B testing
const variant = manager.getFeatureVariant("checkout-cta");
renderCTA(variant ?? "control");

Maintenance mode

const manager = new SiteManager({
  apiKey: process.env.NEXT_PUBLIC_FEATURELY_API_KEY!,
  projectId: process.env.NEXT_PUBLIC_PROJECT_ID!,
  userEmail: user?.email,
  onMaintenanceEnabled: () => router.push("/maintenance"),
  onMaintenanceDisabled: () => router.push("/"),
});
await manager.init();

Status message banners (custom UI)

// Disable auto-injection to render your own components
const manager = new SiteManager({
  apiKey: process.env.NEXT_PUBLIC_FEATURELY_API_KEY!,
  projectId: process.env.NEXT_PUBLIC_PROJECT_ID!,
  autoInjectBanners: false,
  onMessageReceived: (message) => {
    showBanner({ title: message.title, type: message.type });
  },
});
await manager.init();

App version checking

const manager = new SiteManager({
  apiKey: process.env.NEXT_PUBLIC_FEATURELY_API_KEY!,
  projectId: process.env.NEXT_PUBLIC_PROJECT_ID!,
  appVersion: "1.2.3",
  enableVersionCheck: true,
  platform: "web",
  onUpdateRequired: (info) => {
    alert(`Update required: ${info.latestVersion?.version}`);
  },
  onUpdateAvailable: (info) => {
    showUpdateBanner(info.latestVersion?.version ?? "");
  },
});
await manager.init();

Advanced Analytics

const manager = new SiteManager({
  apiKey: process.env.NEXT_PUBLIC_FEATURELY_API_KEY!,
  projectId: process.env.NEXT_PUBLIC_PROJECT_ID!,
  enableAnalytics: true,
  enableHeatmaps: true, // Click heatmaps
  enableRageClickDetection: true, // Frustration detection
  enableScrollTracking: true, // Scroll depth events
  enablePerformanceTracking: true, // Resource timing & long tasks
  heatmapSampleRate: 10, // Sample 10% of clicks
});
await manager.init();

// Track revenue
manager.trackRevenue("purchase", 4999, "USD", {
  productId: "pro-plan",
  quantity: 1,
});

// Track custom events
manager.trackEvent("signup_completed", {
  plan: "pro",
  source: "landing-page",
});

Custom analytics events

manager.trackEvent("checkout_started", { cartValue: 99.99 });
manager.trackEvent("button_clicked", { button: "signup", page: "/home" });

Debug overlay

// Always on in staging; accessible via URL param in production
const manager = new SiteManager({
  apiKey: process.env.NEXT_PUBLIC_FEATURELY_API_KEY!,
  projectId: process.env.NEXT_PUBLIC_PROJECT_ID!,
  debugMode: process.env.NODE_ENV !== "production",
  debugSecret: "my-secret", // visit ?ft_debug=my-secret in production
});

React / Next.js App Router

"use client";
import { useEffect } from "react";
import { SiteManager } from "featurely-site-manager";

let manager: SiteManager | null = null;

export function SiteManagerProvider({
  children,
  user,
}: {
  children: React.ReactNode;
  user?: { id: string; email: string };
}) {
  useEffect(() => {
    if (!manager) {
      manager = new SiteManager({
        apiKey: process.env.NEXT_PUBLIC_FEATURELY_API_KEY!,
        projectId: process.env.NEXT_PUBLIC_PROJECT_ID!,
        userId: user?.id,
        userEmail: user?.email,
      });
      manager.init();
    }
    return () => {
      manager?.destroy();
      manager = null;
    };
  }, [user]);

  return <>{children}</>;
}

Environment Support

| Environment | Supported | Notes | | ----------------------- | --------- | ---------------------------------------------------------------------- | | Browser | ✅ | Full support — polling, analytics, banners, debug overlay | | Node.js / SSR | ✅ | Use environment config to bypass window detection; no DOM features | | React Server Components | ✅ | Safe to import; call init() only in Client Components | | Edge / Workers | ⚠️ | Config fetch only; no polling or DOM injection |

Ships as CJS + ESM with TypeScript declarations.

Framework Integrations

React / Next.js App Router

Use a singleton pattern to ensure init() runs once:

"use client";
import { useEffect } from "react";
import { SiteManager } from "featurely-site-manager";

let manager: SiteManager | null = null;

export function SiteManagerProvider({
  children,
  user,
}: {
  children: React.ReactNode;
  user?: { id: string; email: string };
}) {
  useEffect(() => {
    if (!manager) {
      manager = new SiteManager({
        apiKey: process.env.NEXT_PUBLIC_FEATURELY_API_KEY!,
        projectId: process.env.NEXT_PUBLIC_PROJECT_ID!,
        userId: user?.id,
        userEmail: user?.email,
      });
      manager.init();
    }
    return () => {
      manager?.destroy();
      manager = null;
    };
  }, [user]);

  return <>{children}</>;
}

Debugging / Local Development

const manager = new SiteManager({
  apiKey: process.env.NEXT_PUBLIC_FEATURELY_API_KEY!,
  projectId: process.env.NEXT_PUBLIC_PROJECT_ID!,
  // Floating overlay in development; URL param in production
  debugMode: process.env.NODE_ENV !== "production",
  debugSecret: "my-dev-secret", // visit ?ft_debug=my-dev-secret in prod
  pollInterval: 5000, // faster polling during development
});
await manager.init();

// Override a flag locally without touching the dashboard
manager.overrideFlag("new-checkout", true);

// URL param override (works in any environment):
// visit /your-page?ft_new-checkout=true

TypeScript

All public types are exported from the package entry point:

import type {
  SiteManagerConfig, // Full constructor config
  VersionUpdateRules, // major/minor/patch update classification
  FeatureFlag, // flag object returned by getAllFeatureFlags()
  StatusMessage, // message object returned by getActiveMessages()
  MaintenanceConfig, // passed to onMaintenanceEnabled callback
  VersionCheckResponse, // passed to onUpdateAvailable / onUpdateRequired
} from "featurely-site-manager";

Resources