@zenmanage/sdk
v3.1.2
Published
Feature flags SDK for JavaScript/TypeScript - works in Node.js and browsers
Downloads
255
Maintainers
Readme
Zenmanage JavaScript SDK
Add feature flags to your JavaScript/TypeScript application in minutes. Control feature rollouts, A/B test, and manage configurations without deploying code. Works in both Node.js and browsers!
Entry Points
The SDK ships two entry points:
| Import | Environment | Includes |
| --------------------- | --------------------- | -------------------------------------- |
| @zenmanage/sdk | Browser + Node.js | Core SDK, InMemoryCache, NullCache |
| @zenmanage/sdk/node | Node.js only | Everything above + FileSystemCache |
Use @zenmanage/sdk for browser apps or universal code. Use @zenmanage/sdk/node when you need filesystem caching in a Node.js server.
Why Zenmanage?
- 🚀 Fast: Rules cached locally - ~1ms evaluation time
- 🎯 Targeted: Roll out features to specific users, organizations, or segments
- 🛡️ Safe: Graceful fallbacks and error handling built-in
- 📊 Insightful: Automatic usage tracking (optional)
- 🧪 Testable: Easy to mock in tests
- 🌐 Universal: Works in Node.js (16+) and modern browsers
- 📘 TypeScript: Full type definitions included
Installation
npm install @zenmanage/sdkBrowser Requirements: Modern browsers with fetch API support (or use a polyfill) Node.js Requirements: Node.js 16+ (requires native fetch API)
Key Compatibility
- Browser/client runtime: client keys prefixed with
cli_ - Node.js/server runtime: server keys prefixed with
srv_ - Mobile keys (
mob_) are rejected by this SDK at initialization
Get Started in 60 Seconds
1. Get your environment token from zenmanage.com
2. Initialize the SDK:
import { Zenmanage, ConfigBuilder } from '@zenmanage/sdk';
const zenmanage = new Zenmanage(
ConfigBuilder.create().withEnvironmentToken('cli_your_client_key_here').build()
);3. Check a feature flag:
const flag = await zenmanage.flags().single('new-dashboard');
if (flag.isEnabled()) {
// Show new dashboard
showNewDashboard();
} else {
// Show old dashboard
showOldDashboard();
}That's it! 🎉
Common Use Cases
Roll Out a New Feature Gradually
import { Zenmanage, ConfigBuilder, Context } from '@zenmanage/sdk';
const zenmanage = new Zenmanage(
ConfigBuilder.create().withEnvironmentToken('cli_your_client_key_here').build()
);
// Create context with user information
const context = Context.single('user', userId, userName);
const betaAccess = await zenmanage.flags().withContext(context).single('beta-program');
if (betaAccess.isEnabled()) {
// User is in beta program
const features = getBetaFeatures();
}Note: Call withContext() on the flag manager to ensure context is sent to the API when loading rules.
A/B Testing
import { Context, Attribute } from '@zenmanage/sdk';
const context = new Context('user', user.name, user.id, [
new Attribute('country', [user.country]),
new Attribute('plan', [user.subscriptionPlan]),
]);
const variant = await zenmanage.flags().withContext(context).single('checkout-flow');
if (variant.asString() === 'one-page') {
renderOnePageCheckout();
} else {
renderMultiPageCheckout();
}Percentage Rollouts
Gradually roll out features to a percentage of your users. The SDK handles bucketing automatically using a deterministic CRC32B hash — no manual bucket logic needed.
import { Context } from '@zenmanage/sdk';
// Just provide a context with an identifier — the SDK does the rest
const context = Context.single('user', userId);
const flag = await zenmanage.flags().withContext(context).single('new-checkout-flow');
if (flag.isEnabled()) {
// This user is in the rollout percentage
renderNewCheckout();
} else {
// This user is outside the rollout
renderClassicCheckout();
}How it works:
- Configure the rollout percentage (0–100%) and a unique salt in the Zenmanage dashboard
- The SDK hashes
salt:contextIdentifierto deterministically assign each user to a bucket (0–99) - Users whose bucket is below the percentage get the rollout value; others get the fallback
- The same user always gets the same result (deterministic), and increasing the percentage never removes previously included users
- Rollout rules can further refine targeting within the rollout group (e.g., only US users in the rollout)
Note: A context
identifieris required for bucketing. Without one, the user always receives the fallback value.
Feature Toggles by Organization
const orgContext = Context.single('organization', orgId, orgName);
const enterpriseFeatures = await zenmanage
.flags()
.withContext(orgContext)
.single('enterprise-analytics');
if (enterpriseFeatures.isEnabled()) {
showEnterpriseAnalytics();
}Using Default Values
// Inline default (highest priority)
const flag = await zenmanage.flags().single('feature-flag', true);
console.log(flag.isEnabled()); // Returns true if flag not found
// Or use DefaultsCollection for multiple defaults
import { DefaultsCollection } from '@zenmanage/sdk';
const defaults = DefaultsCollection.fromObject({
'new-ui': true,
'api-version': 'v2',
'max-items': 100,
});
const flagManager = zenmanage.flags().withDefaults(defaults);
const flag = await flagManager.single('new-ui');Get All Flags
const allFlags = await zenmanage.flags().all();
for (const flag of allFlags) {
console.log(`${flag.getKey()}: ${flag.getValue()}`);
}Configuration Options
const config = ConfigBuilder.create()
.withEnvironmentToken('cli_your_client_key_here') // Browser/client runtime
// For Node.js/server runtime use: .withEnvironmentToken('srv_your_server_key_here')
.withCacheTtl(3600) // Cache TTL in seconds (default: 3600)
.withCacheBackend('memory') // 'memory' or 'null' (default: 'memory')
.withCache(customCacheInstance) // Custom Cache implementation (overrides cacheBackend)
.withUsageReporting(true) // Enable usage tracking (default: true)
.withApiEndpoint('https://api.zenmanage.com') // Custom API endpoint (default: api.zenmanage.com)
.withLogger(customLogger) // Custom logger instance
.build();
const zenmanage = new Zenmanage(config);Configuration from Environment Variables (Node.js only)
// Reads from environment variables:
// - ZENMANAGE_ENVIRONMENT_TOKEN
// - ZENMANAGE_CACHE_TTL
// - ZENMANAGE_CACHE_BACKEND
// - ZENMANAGE_CACHE_DIR
// - ZENMANAGE_ENABLE_USAGE_REPORTING
// - ZENMANAGE_API_ENDPOINT
const config = ConfigBuilder.fromEnvironment().build();
const zenmanage = new Zenmanage(config);Cache Backends
Memory Cache (Default)
Best for: Most applications, serverless functions, browsers
ConfigBuilder.create()
.withEnvironmentToken('cli_your_client_key_here')
.withCacheBackend('memory')
.build();Data is cached in memory for the lifetime of the application. Fastest option but data is lost on restart. Works everywhere (Node.js and browsers).
Filesystem Cache (Node.js only)
Best for: Long-running Node.js servers
The filesystem cache is available from the @zenmanage/sdk/node entry point:
import { Zenmanage, ConfigBuilder, FileSystemCache } from '@zenmanage/sdk/node';
const config = ConfigBuilder.create()
.withEnvironmentToken('srv_your_server_key_here')
.withCache(new FileSystemCache('/tmp/zenmanage-cache'))
.withCacheTtl(7200)
.build();Data persists across application restarts. Only available in Node.js — it is not included in the default @zenmanage/sdk import to keep the browser bundle free of Node.js dependencies.
Custom Cache
You can provide any object that implements the Cache interface via .withCache():
import type { Cache } from '@zenmanage/sdk';
class RedisCache implements Cache {
async get(key: string): Promise<string | null> {
/* ... */
}
async set(key: string, value: string, ttl?: number): Promise<void> {
/* ... */
}
async has(key: string): Promise<boolean> {
/* ... */
}
async delete(key: string): Promise<void> {
/* ... */
}
async clear(): Promise<void> {
/* ... */
}
}
const config = ConfigBuilder.create()
.withEnvironmentToken('cli_your_client_key_here')
.withCache(new RedisCache())
.build();Null Cache (No Caching)
Best for: Testing, debugging
ConfigBuilder.create()
.withEnvironmentToken('cli_your_client_key_here')
.withCacheBackend('null')
.build();Fetches rules from API on every request. Useful for testing but not recommended for production.
Context
Context allows you to pass information about the user, organization, or other entities that influence flag evaluation based on server-side rules.
Simple Context
import { Context } from '@zenmanage/sdk';
// Context with just identifier
const context = Context.single('user', 'user-123');
// Context with identifier and name
const context = Context.single('user', 'user-123', 'John Doe');Context with Attributes
import { Context, Attribute } from '@zenmanage/sdk';
const context = new Context(
'user', // type
'John Doe', // name (optional)
'user-123', // identifier (optional)
[
new Attribute('country', ['US']),
new Attribute('plan', ['premium', 'annual']),
new Attribute('signup_date', ['2024-01-15']),
]
);
const flag = await zenmanage.flags().withContext(context).single('premium-feature');Context Types
- user: Individual users
- organization: Companies or teams
- session: Browser or app sessions
- device: Physical devices
- custom: Any custom type you define
Rule Targeting With Context Types
When rules target context or segment, the SDK compares the context identifier and, if present, the type. If a rule value omits type (or sets it to null), only the identifier is matched.
const rules = [
{
clauses: [
{
attribute: 'context',
operator: 'equals',
value: { identifier: 'user-123', type: 'user' },
},
],
value: { value: { boolean: true } },
},
{
clauses: [
{
attribute: 'segment',
operator: 'equals',
value: { identifier: 'shared-id', type: null },
},
],
value: { value: { boolean: true } },
},
];Flag Types
Boolean Flags
const flag = await zenmanage.flags().single('feature-enabled');
if (flag.isEnabled()) {
// Feature is enabled
}
// Or use asBool()
const enabled = flag.asBool();String Flags
const flag = await zenmanage.flags().single('theme');
const theme = flag.asString();
console.log(theme); // 'dark', 'light', etc.Number Flags
const flag = await zenmanage.flags().single('max-items');
const maxItems = flag.asNumber();
console.log(maxItems); // 100, 250, etc.Type-Safe Access
All flags have multiple accessor methods:
isEnabled(): Boolean check (only true for boolean flags with valuetrue)asBool(): Get value as booleanasString(): Get value as stringasNumber(): Get value as numbergetValue(): Get raw value
Advanced Features
Custom Logger
import type { Logger } from '@zenmanage/sdk';
const customLogger: Logger = {
debug: (msg, meta) => console.debug(msg, meta),
info: (msg, meta) => console.info(msg, meta),
warn: (msg, meta) => console.warn(msg, meta),
error: (msg, meta) => console.error(msg, meta),
};
const config = ConfigBuilder.create()
.withEnvironmentToken('srv_your_server_key_here')
.withLogger(customLogger)
.build();Force Refresh Rules
// Force refresh from API (bypasses cache)
await zenmanage.flags().refreshRules();Manual Usage Reporting
const context = Context.single('user', 'user-123');
// Report that a flag was evaluated
await zenmanage.flags().reportUsage('feature-flag', context);TypeScript Support
The SDK is written in TypeScript and includes full type definitions:
import type {
Config,
Logger,
Cache,
FlagType,
FlagValue,
ContextData,
RulesResponse,
} from '@zenmanage/sdk';
const config: Config = {
environmentToken: 'srv_test',
cacheTtl: 3600,
cacheBackend: 'memory',
};Browser Usage
The default @zenmanage/sdk entry point is fully browser-safe — it contains no Node.js built-ins (fs, path, util), so it works with any bundler (Webpack, Vite, Rollup, esbuild, etc.) and from a CDN.
<!DOCTYPE html>
<html>
<head>
<script type="module">
import { Zenmanage, ConfigBuilder } from 'https://cdn.skypack.dev/@zenmanage/sdk';
const zenmanage = new Zenmanage(
ConfigBuilder.create().withEnvironmentToken('srv_your_server_key_here').build()
);
const flag = await zenmanage.flags().single('new-ui');
if (flag.isEnabled()) {
document.body.classList.add('new-ui');
}
</script>
</head>
<body>
<!-- Your content -->
</body>
</html>With a bundler (Webpack, Vite, etc.):
import { Zenmanage, ConfigBuilder } from '@zenmanage/sdk';
const zenmanage = new Zenmanage(
ConfigBuilder.create().withEnvironmentToken('srv_your_server_key_here').build()
);Note:
FileSystemCacheis only available from@zenmanage/sdk/node. In the browser, use the default in-memory cache or provide a customCacheimplementation.
Error Handling
import { EvaluationError, FetchRulesError } from '@zenmanage/sdk';
try {
const flag = await zenmanage.flags().single('unknown-flag');
} catch (error) {
if (error instanceof EvaluationError) {
// Flag not found
console.error('Flag not found:', error.message);
} else if (error instanceof FetchRulesError) {
// API error
console.error('Failed to fetch rules:', error.message);
}
}
// Or use default values to avoid errors
const flag = await zenmanage.flags().single('unknown-flag', false);
console.log(flag.isEnabled()); // falseTesting
The SDK is designed to be easily testable:
import { Zenmanage, ConfigBuilder } from '@zenmanage/sdk';
import { vi } from 'vitest'; // or jest
// Mock the API client
vi.mock('@zenmanage/sdk', async () => {
const actual = await vi.importActual('@zenmanage/sdk');
return {
...actual,
Zenmanage: vi.fn().mockImplementation(() => ({
flags: () => ({
single: vi.fn().mockResolvedValue({
isEnabled: () => true,
asString: () => 'test-value',
asNumber: () => 42,
}),
}),
})),
};
});
test('feature flag enabled', async () => {
const zenmanage = new Zenmanage(ConfigBuilder.create().withEnvironmentToken('srv_test').build());
const flag = await zenmanage.flags().single('test-flag');
expect(flag.isEnabled()).toBe(true);
});Best Practices
1. Initialize Once
Create a single Zenmanage instance and reuse it throughout your application:
// zenmanage.ts (Node.js)
import { Zenmanage, ConfigBuilder } from '@zenmanage/sdk/node';
export const zenmanage = new Zenmanage(
ConfigBuilder.create().withEnvironmentToken(process.env.ZENMANAGE_TOKEN!).build()
);
// zenmanage.ts (Browser)
import { Zenmanage, ConfigBuilder } from '@zenmanage/sdk';
export const zenmanage = new Zenmanage(
ConfigBuilder.create().withEnvironmentToken('srv_your_server_key_here').build()
);
// other-file.ts
import { zenmanage } from './zenmanage';
const flag = await zenmanage.flags().single('feature');2. Use Default Values
Always provide default values to ensure your application works even if the API is unavailable:
const flag = await zenmanage.flags().single('feature', false);3. Context Best Practices
- Always include context when evaluating flags that have targeting rules
- Use consistent attribute names across your application
- Keep attribute values simple (strings, numbers)
4. Cache Configuration
- Use memory cache for most applications, serverless, and browsers (default)
- Use filesystem cache for long-running Node.js servers (import from
@zenmanage/sdk/node) - Use custom cache (
.withCache()) for Redis, IndexedDB, or other backends - Use null cache only for testing/debugging
5. Error Handling
Always handle errors gracefully with try-catch or default values:
try {
const flag = await zenmanage.flags().single('feature');
if (flag.isEnabled()) {
// Enable feature
}
} catch (error) {
// Fallback to safe default
console.error('Flag evaluation failed:', error);
}API Reference
Zenmanage
Main SDK class.
Methods:
flags(): Returns the FlagManager instance
ConfigBuilder
Fluent builder for creating configuration.
Methods:
create(): Create new builderfromEnvironment(): Create builder from environment variables (Node.js only)withEnvironmentToken(token): Set environment token (required)withCacheTtl(seconds): Set cache TTLwithCacheBackend(backend): Set cache backend ('memory'or'null')withCacheDirectory(path): Set cache directory (used with filesystem cache)withCache(cache): Set a customCacheinstance (overridescacheBackend)withUsageReporting(enabled): Enable or disable usage trackingwithApiEndpoint(url): Set custom API endpointwithLogger(logger): Set custom loggerbuild(): Build the configuration
FlagManager
Manages flag evaluation.
Methods:
single(key, defaultValue?): Get a single flag by keyall(): Get all flagswithContext(context): Create new manager with contextwithDefaults(defaults): Create new manager with defaultsrefreshRules(): Force refresh rules from APIreportUsage(key, context?): Manually report flag usage
Context
Represents evaluation context.
Methods:
single(type, identifier, name?): Create simple contextfromObject(data): Create from plain objectaddAttribute(attribute): Add an attributegetAttribute(key): Get an attributehasAttribute(key): Check if attribute existsgetAttributes(): Get all attributes
Flag
Represents a feature flag.
Methods:
isEnabled(): Check if boolean flag is enabledasBool(): Get value as booleanasString(): Get value as stringasNumber(): Get value as numbergetValue(): Get raw valuegetKey(): Get flag keygetName(): Get flag namegetType(): Get flag type
Examples
See the examples directory for more examples:
- simple-flags.ts - Basic flag operations
- context-based-flags.ts - Using context for targeting
- ab-testing.ts - A/B testing example
- defaults.ts - Using default values
- caching.ts - Cache configuration examples
- percentage-rollouts.ts - SDK-side percentage rollouts
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
License
MIT License - see LICENSE for details.
Support
- Documentation: https://zenmanage.com/resources
- Issues: GitHub Issues
- Email: [email protected]
Changelog
See CHANGELOG.md for release history and changes.
