A lightweight, feature-rich client-side SDK for FlipFlag (https://flipflag.dev) — a powerful feature flag management platform.
The SDK is designed to be simple, declarative, and safe by default. It supports both browser and Node.js environments with automatic feature flag synchronization and usage tracking.
npm install @flipflag/sdk
# or
yarn add @flipflag/sdk
# or
pnpm add @flipflag/sdkimport { FlipFlag } from "@flipflag/sdk";
const manager = new FlipFlag({
publicKey: "YOUR_PUBLIC_KEY",
privateKey: "YOUR_PRIVATE_KEY", // optional (read-only mode without it)
});
await manager.init();
if (manager.isEnabled("newFeature")) {
console.log("Feature is enabled!");
}import { FlipFlag } from "@flipflag/sdk";
const manager = new FlipFlag(
{
publicKey: "YOUR_PUBLIC_KEY",
privateKey: "YOUR_PRIVATE_KEY",
},
{
// Initial configuration
newFeature: {
contributor: "dev@example.com",
},
}
);
await manager.init();- ✅ Dual environment support: Node.js and Browser
- ✅ Read-only mode: Use only
publicKeyto fetch flags - ✅ Full management: Use
privateKeyto declare features and track usage - ✅ YAML configuration: Declarative feature definitions (Node.js)
- ✅ Programmatic API: Declare features via code (Browser)
- ✅ Auto-sync: Periodic polling for flags and statistics
- ✅ Usage tracking: Automatic feature usage analytics
- ✅ Experiment metrics: Track assignments, exposures, and custom metric events
- ✅ TypeScript: Full type safety out of the box
- ✅ Lightweight: Minimal dependencies
interface IManagerOptions {
/**
* FlipFlag API base URL.
* @default "https://api.flipflag.dev"
*/
apiUrl?: string;
/**
* Optional backup API base URL used only for GET /v1/sdk/feature/flags.
* Write operations always stay on apiUrl.
*/
flagsFallbackApiUrl?: string;
/**
* Public key for fetching feature flags (required).
* Get it from your FlipFlag project settings.
*/
publicKey: string;
/**
* Private key for declaring features and syncing usage (optional).
* Without it, SDK works in read-only mode.
*/
privateKey?: string;
/**
* Path to .flipflag.yml config file (Node.js only).
* @default "<process.cwd()>/.flipflag.yml"
*/
configPath?: string;
/**
* If true, missing config file won't throw an error.
* @default true
*/
ignoreMissingConfig?: boolean;
/**
* Polling interval for fetching feature flags (milliseconds).
* @default 30000 (30 seconds)
*/
pollingInterval?: number;
/**
* Sync interval for sending declarations and usage stats (milliseconds).
* @default 90000 (90 seconds)
*/
syncInterval?: number;
}Perfect for client-side applications where you only need to check feature states:
const manager = new FlipFlag({
publicKey: "pub_xxxxxxxxxxxxx",
});
await manager.init();
// Check feature state
if (manager.isEnabled("darkMode")) {
enableDarkMode();
}
// Cleanup when done
manager.destroy();For applications that manage features and track usage:
const manager = new FlipFlag({
publicKey: "pub_xxxxxxxxxxxxx",
privateKey: "priv_xxxxxxxxxxxxx",
});
await manager.init();
// Features will be automatically declared from .flipflag.yml
// and usage will be trackedconst manager = new FlipFlag({
publicKey: "pub_xxxxxxxxxxxxx",
pollingInterval: 10_000, // Check flags every 10 seconds
syncInterval: 60_000, // Sync usage every 60 seconds
});Use a separate backup base URL only for reading flags when the primary SDK API is temporarily unavailable:
const manager = new FlipFlag({
publicKey: "pub_xxxxxxxxxxxxx",
privateKey: "priv_xxxxxxxxxxxxx",
apiUrl: "https://api.flipflag.dev",
flagsFallbackApiUrl: "https://sdk-backup.flipflag.dev",
});The SDK falls back to flagsFallbackApiUrl only for GET /v1/sdk/feature/flags and only on network errors or 5xx responses. 401, 403, and other client errors still come from the primary API and are not retried against backup.
Force immediate synchronization without waiting for intervals:
await manager.init();
// ... some time later
await manager.sync(); // Syncs declarations and usage immediatelyimport { FlipFlag } from "@flipflag/sdk";
const manager = new FlipFlag(
{
publicKey: "pub_xxxxxxxxxxxxx",
privateKey: "priv_xxxxxxxxxxxxx",
},
{
newPaymentFlow: {
description: "New Stripe integration",
contributor: "payments-team@example.com",
type: "feature",
},
legacyCheckout: {
contributor: "payments-team@example.com",
},
}
);
await manager.init();const manager = new FlipFlag({
publicKey: "pub_xxxxxxxxxxxxx",
privateKey: "priv_xxxxxxxxxxxxx",
});
await manager.init();
// Add features programmatically
manager.declareFromObject({
experimentalUI: {
contributor: "ui-team@example.com",
},
});
// Sync immediately
await manager.sync();Track experiment assignments, exposures, and custom metric events. Events are buffered in memory and automatically flushed every syncInterval (default 90s) or immediately when the buffer reaches 500 events.
Requires privateKey.
Record when a subject is assigned to a variant:
manager.trackAssignment({
experimentKey: "checkout-flow-v2",
variant: "control",
subjectType: "user",
subjectId: "user_123",
});Record when a subject actually sees the variant (e.g. when the UI renders):
manager.trackExposure({
experimentKey: "checkout-flow-v2",
variant: "treatment",
subjectType: "user",
subjectId: "user_123",
});Record a custom metric event. metricName is required:
// Conversion event (value defaults to 1)
manager.trackMetric({
experimentKey: "checkout-flow-v2",
variant: "treatment",
subjectType: "user",
subjectId: "user_123",
metricName: "purchase_completed",
});
// Revenue metric with numeric value
manager.trackMetric({
experimentKey: "checkout-flow-v2",
variant: "treatment",
subjectType: "user",
subjectId: "user_123",
metricName: "revenue",
value: 49.99,
properties: { currency: "USD", plan: "pro" },
});| Value | Description |
|---|---|
"user" |
Authenticated user |
"device" |
Anonymous device |
"session" |
Browser/app session |
"org" |
Organization / workspace |
The SDK automatically loads .flipflag.yml from your project root during init().
newFeature:
contributor: epolevov@emd.one
anotherFeature:
contributor: dev@company.comseasonalFeature:
description: "Holiday sale banner"
contributor: marketing@example.com
type: "feature"
betaFeature:
description: "New analytics dashboard"
contributor: analytics-team@example.com
type: "experiment"const manager = new FlipFlag({
publicKey: "pub_xxxxxxxxxxxxx",
configPath: "./config/features.yml",
});// Node.js
new FlipFlag(options: IManagerOptions)
// Browser
new FlipFlag(options: IManagerOptions, initialConfig?: FlipFlagYaml)Initializes the SDK:
- Loads YAML config (Node.js only)
- Fetches current feature flags from server
- Syncs declared features
- Starts automatic polling and sync intervals
await manager.init();Checks if a feature is enabled. Also:
- Tracks usage automatically
- Creates feature on server if it doesn't exist (requires
privateKey)
if (manager.isEnabled("darkMode")) {
// Feature is enabled
}Programmatically declares features without YAML file.
manager.declareFromObject({
myFeature: {
contributor: "dev@example.com",
},
});Manually triggers synchronization:
- Syncs feature declarations
- Sends usage statistics
await manager.sync();Stops all timers and clears local state:
- Stops polling interval
- Stops sync interval
- Clears cached flags, usage data, and metrics queue
manager.destroy();Records an assignment event — when a subject is assigned to a variant.
manager.trackAssignment({
experimentKey: "my-experiment",
variant: "control",
subjectType: "user",
subjectId: "user_123",
properties: { source: "onboarding" }, // optional
});Records an exposure event — when a subject actually sees the variant.
manager.trackExposure({
experimentKey: "my-experiment",
variant: "treatment",
subjectType: "user",
subjectId: "user_123",
});Records a custom metric event. metricName is required. value defaults to 1 on the server if omitted.
manager.trackMetric({
experimentKey: "my-experiment",
variant: "treatment",
subjectType: "user",
subjectId: "user_123",
metricName: "revenue",
value: 29.99,
properties: { plan: "pro" }, // optional
});type FlipFlagYaml = Record<string, YamlFeature>;
interface YamlFeature {
description?: string;
contributor?: string;
type?: string;
}interface IFeatureFlag {
enabled: boolean;
}type MetricEventType = 'assignment' | 'exposure' | 'metric';
type MetricSubjectType = 'user' | 'device' | 'session' | 'org';
interface IMetricEvent {
eventId: string; // auto-generated by SDK
experimentKey: string;
variant: string;
subjectType: MetricSubjectType;
subjectId: string;
eventType: MetricEventType;
metricName?: string; // required for eventType = 'metric'
value?: number;
properties?: Record<string, unknown>;
clientTs?: string; // auto-set by SDK (ISO 8601)
}// For trackAssignment() and trackExposure()
type ITrackEventParams = Omit<IMetricEvent, 'eventId' | 'eventType'>;
// For trackMetric() — metricName is required
type ITrackMetricParams = ITrackEventParams & { metricName: string };const manager = new FlipFlag({
publicKey: process.env.FLIPFLAG_PUBLIC_KEY!,
privateKey: process.env.FLIPFLAG_PRIVATE_KEY,
});// app.ts
const manager = new FlipFlag({ /* ... */ });
await manager.init();
// Now use throughout your app
export { manager };process.on("SIGTERM", () => {
manager.destroy();
process.exit(0);
});For security, only use publicKey in client-side production code:
// ✅ Good - read-only
const manager = new FlipFlag({
publicKey: import.meta.env.VITE_FLIPFLAG_PUBLIC_KEY,
});
// ❌ Bad - exposes private key
const manager = new FlipFlag({
publicKey: import.meta.env.VITE_FLIPFLAG_PUBLIC_KEY,
privateKey: import.meta.env.VITE_FLIPFLAG_PRIVATE_KEY, // Don't do this!
});try {
await manager.init();
} catch (error) {
console.error("Failed to initialize FlipFlag:", error);
// Fallback to default behavior
}// Default to false if something goes wrong
const isEnabled = manager?.isEnabled("newFeature") ?? false;Error: FlipFlag: cannot read config at /path/to/.flipflag.yml: ENOENT
Solution: Set ignoreMissingConfig: true or create the config file:
const manager = new FlipFlag({
publicKey: "pub_xxxxxxxxxxxxx",
ignoreMissingConfig: true,
});Error: Public key is missing. Please provide a valid publicKey in the SDK configuration.
Solution: Always provide a publicKey:
const manager = new FlipFlag({
publicKey: "pub_xxxxxxxxxxxxx", // Required!
});If features declared in YAML aren't appearing on the server:
- Check that you provided
privateKey(read-only mode can't create features) - Check network connectivity to FlipFlag API
- Verify your API keys are correct
- Check browser/server console for errors
- Node.js: v16+ (ESM and CommonJS)
- Browsers: Modern browsers with
fetchAPI support - TypeScript: 4.5+
The package provides optimized builds for different environments:
{
"exports": {
".": {
"types": "./dist/browser.d.ts",
"browser": "./dist/browser.js",
"node": "./dist/node.js",
"default": "./dist/browser.js"
}
}
}Node.js automatically uses the Node-specific build with filesystem support, while browsers get the lightweight browser build.
- Website: https://flipflag.dev
- Documentation: https://docs.flipflag.dev
- GitHub: https://github.com/flipflag-dev/sdk
- NPM: https://www.npmjs.com/package/@flipflag/sdk
- Issues: https://github.com/flipflag-dev/sdk/issues
MIT License - see LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.
Made with ❤️ by the FlipFlag team