featurely-site-manager
v1.4.1
Published
Complete site management SDK for maintenance mode, status messages, feature flags, version checking, and analytics
Maintainers
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-managerRequired 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=trueTypeScript
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";