Skip to main content

Vanilla JavaScript SDK

Guided sample​

Open the JavaScript sample, follow the first-toggle exercise in its README, then read src/demo.ts (flag keys and offline fixture), src/sample-app.ts (initialization, user and Order context), and tests/published-sdk.test.ts (browser SDK checks). Wait for Toggly.init() before evaluating flags.

The sample's VITE_ settings are public browser build inputs. The App Key identifies the definitions application; do not put management API credentials or secrets in those settings.

Use Toggly's JavaScript SDK in plain JavaScript, HTML, and CSS applications.

Installation​

Embed the browser bundle from the CDN:

<script src="https://cdn.jsdelivr.net/npm/@ops-ai/feature-flags-toggly/dist/feature-flags-toggly.bundle.js"></script>

Alternatively, install the package and serve its browser bundle.

$ npm install @ops-ai/feature-flags-toggly

Copy node_modules/@ops-ai/feature-flags-toggly/dist/feature-flags-toggly.bundle.js into your public assets and include it in your HTML. No build inside node_modules is required. The bundle exposes window.Toggly; use that browser global in the examples below.

<script src="./path/to/feature-flags-toggly.bundle.js"></script>
Client-Side Only

For browser applications, use only the App Key. Never expose API keys in client-side code.

Initialization​

Initialize Toggly by running the Toggly.init method and by providing your App Key from your Toggly application page

Toggly.init({
appKey: '<YOUR_APP_KEY>',
environment: '<YOUR_APP_ENVIRONMENT>'
})
.then(function (flags) {
// Now you can check if a feature (or more) is Enabled/Disabled

if (Toggly.isFeatureOn('SignUpButton')) {
// SignUpButton is ON
}

if (Toggly.isFeatureOff('DemoScreenshot')) {
// DemoScreenshot is OFF
}
});

Configuration Options​

You can customize Toggly's behavior using configuration options:

Toggly.init({
appKey: '<YOUR_APP_KEY>',
environment: '<YOUR_APP_ENVIRONMENT>',
baseURI: 'https://definitions.toggly.io', // Default API URL
connectTimeout: 5000, // Connection timeout in milliseconds (default: 5000)
featureFlagsRefreshInterval: 180000, // Refresh interval in milliseconds (default: 3 minutes)
isDebug: false, // Enable debug logging (default: false)
reloadOnFeatureFlagValidation: false, // Reload page when flags change (default: false)
enableLiveUpdates: true, // WebSocket live updates (default: true)
verifySignatures: false, // Verify ES256 signed envelopes (default: false)
// allowedKeyIds: ['kid-1'], // Optional JWKS kid allow-list when verifySignatures is true
// maxSignatureAgeSeconds: 300, // Optional signature freshness window
onError: function (message, error) {
// Report SDK failures to your app logger, Sentry, or Datadog
console.warn('Toggly error:', message, error);
},
persistCache: true, // Cache flags/variants in localStorage (default: true)
maxCacheKeys: 20, // Optional LRU cap for identity-scoped cache keys (default: unlimited)
flagDefaults: {
"Feature1": true,
"Feature2": false
}
});

maxCacheKeys limits how many identity-scoped flags/variants localStorage keys are retained. When variants are enabled, flags and variants for the same evaluation context are protected together during eviction so a refresh cannot drop one half of the pair. See Client-side cache limits.

Auto-Generated Identity​

Identity is stored in origin-wide localStorage when available. It survives reloads and browser restarts until cleared and is shared by SDK apps on that origin; it is not tab-scoped or scoped by App Key. persistCache: false disables definition caching, not context persistence. When storage is unavailable, supplied context is retained in memory.

Pass known identity, groups, and claims to init. Omitted fields preserve stored values; when identity is omitted and none is stored, the SDK generates one. An explicit empty identity remains anonymous. Identity and claims are targeting data, not proof of authentication.

Initialization Promise​

The init method returns a Promise that resolves with the loaded feature flags:

Toggly.init({
appKey: '<YOUR_APP_KEY>',
environment: '<YOUR_APP_ENVIRONMENT>'
})
.then(function (flags) {
console.log('Loaded flags:', flags);
// Flags are now available via Toggly.featureFlagsValue
})
.catch(function (error) {
console.error('Failed to initialize Toggly:', error);
});

Directly Checking a Flag​

Check if a flag is on​

if (Toggly.isFeatureOn('SignUpButton')) {
// SignUpButton is ON
}

Check if a flag is off​

if (Toggly.isFeatureOff('DemoScreenshot')) {
// DemoScreenshot is OFF
}

Check if at least one of the listed flags is on​

if (Toggly.evaluateFeatureGate(['ExampleFeatureKey1', 'ExampleFeatureKey2'], 1 /* any */)) {
// AT LEAST ONE the provided feature keys is TRUE
}

Evaluate a feature gate (with requirement & negate support)​

if (Toggly.evaluateFeatureGate(['ExampleFeatureKey1', 'ExampleFeatureKey2'], 0 /* all */, true)) {
// NOT(all): at least one provided feature key is OFF
}

You can also check multiple feature keys and make use of the requirement (0 for all, 1 for any) and negate (bool) options.

Accessing Feature Flags Value​

You can directly access the current feature flags object:

const flags = Toggly.featureFlagsValue;
const isFeatureEnabled = flags['ExampleFeatureKey1'] || false;

This property returns:

  • Cached flags from localStorage (if appKey is provided and flags are cached)
  • Flag defaults (if no appKey or no cache exists)

Error reporting and fallback behavior​

Use onError to observe fetch, cache, parse, and refresh failures without relying on debug-only console output.

Toggly.init({
appKey: '<YOUR_APP_KEY>',
environment: '<YOUR_APP_ENVIRONMENT>',
onError: function (message, error) {
monitoring.captureException(error, {
tags: { source: 'toggly' },
extra: { message }
});
}
});

Toggly.lastError contains the latest SDK error message. It is set before onError runs, so it is safe to read synchronously inside the callback.

After a successful load, refresh failures preserve the last-known-good flags instead of replacing them with defaults or {}. Empty feature maps do not enable non-empty gates by accident; gated checks fail closed unless a flag is explicitly enabled.

For the shared reliability contract, see Reliability and Error Handling.

Entity context​

Target features based on page entities (orders, products) in addition to user rollouts. See Entity & page context.

Client SDKs do not register context kinds with the dashboard — use local mappers only.

Register a mapper​

Toggly.registerContext('Order', function (order) {
return {
kind: 'Order',
key: String(order.id),
attributes: {
Status: order.status,
Total: order.total,
},
};
});

Per-evaluation checks​

Pass the entity on each check (not on global setContext()):

// Detail page
if (Toggly.isFeatureOn('OrderBadge', order, 'Order')) {
showBadge();
}

// List page — one fetch, local gate per row
orders.forEach(function (order) {
var on = Toggly.isFeatureOn('OrderBadge', order, 'Order');
});

Or pass a pre-built context object:

Toggly.isFeatureOn('OrderBadge', {
kind: 'Order',
key: '7',
attributes: { Status: 'red' },
});

Mixed defs and fail closed​

Evaluated-signed may return true, false, or an { requirement, rules } gate object. isFeatureOn resolves gates locally. Without entity context, gates fail closed (off).

Do not use Toggly.featureFlagsValue[key] === true — gate objects are not booleans.

Users and Rollouts​

To facilitate rollouts, and consistently serve the same value for a flag during a session, we need a way of identifying the user, which needs to be supplied by the application

Supply known context before startup​

Use the bundle's window.Toggly browser global. Pass the context together so the first evaluated request targets the intended user without a second startup refresh.

await Toggly.init({
appKey: '<YOUR_APP_KEY>',
environment: 'Production',
identity: 'user-123', // Stable user identifier.
groups: ['beta'], // Memberships.
claims: { plan: 'pro' }, // String rule attributes.
});

The SDK copies supplied context before startup, including when storage is unavailable. Omitted fields preserve stored values; explicit empty groups and claims clear them, and an explicit empty identity stays anonymous.

Change context later​

For login, logout, or membership changes after startup, use the refreshing API:

await Toggly.setContext({
identity: 'user-456',
groups: ['subscribers'],
claims: { plan: 'pro' },
});
// Evaluate the updated context after this resolves.

Assigning Toggly.identity updates identity but does not refresh definitions. Prefer setContext when changing users. await Toggly.clearContext() removes the stored identity, clears groups/claims, resets the in-memory state, and refreshes definitions. It does not generate a new stable anonymous identity; follow the sample's explicit anonymous transition if you need one.

Browser user-agent/language and country filters are evaluated by the Definitions service from the HTTP request. The browser SDK exposes no per-call user-agent or country override. The second/third arguments of isFeatureOn are entity context and kind, not HTTP overrides.

See the Feature filters reference and Evaluated-signed docs for the query contract.

Live updates and signed definitions​

WebSocket live updates are on by default. Set enableLiveUpdates: false to rely on the refresh interval only. When verifySignatures: true, the SDK verifies ES256 envelopes via JWKS before applying flags (see Evaluated-signed and WebSocket sync).

setContext() resets the in-memory evaluated state and refreshes definitions for the new context; direct identity assignment alone does not.

success

When using user identifiers, evaluated features are cached per user for 30 minutes by default on a sliding window, so the user might not see the change right away as to not confuse the user. The session length is configurable and the session store can be cleared in App Settings

Definition Refresh​

Feature definitions are automatically refreshed at the configured interval. The default is 3 minutes, but this can be customized.

Automatic Refresh​

Toggly.init({
appKey: '<YOUR_APP_KEY>',
environment: '<YOUR_APP_ENVIRONMENT>',
featureFlagsRefreshInterval: 3 * 60 * 1000 // 3 minutes (default)
})
.then(function () {
// Flags will automatically refresh every 3 minutes
});

Manual Refresh​

You can manually refresh feature flags from the server:

Toggly.refresh()
.then(function (flags) {
console.log('Flags refreshed:', flags);
})
.catch(function (error) {
console.error('Failed to refresh flags:', error);
// Falls back to cached flags or defaults
});

Managing Refresh Interval​

You can control the automatic refresh interval:

// Start the refresh interval (automatically called during init)
Toggly.startRefreshInterval();

// Cancel the refresh interval
Toggly.cancelRefreshInterval();

If featureFlagsRefreshInterval is set to 0 or negative, automatic refresh is disabled.

Flag Defaults​

In case your application can't reach toggly, you can set feature defaults. These defaults are used when:

  • The app is offline
  • The API request fails
  • No cached flags are available
var featureFlagsDefaults = {
"SignUpButton": true,
"DemoScreenshot": true
};

Toggly.init({
appKey: '<YOUR_APP_KEY>',
environment: '<YOUR_APP_ENVIRONMENT>',
flagDefaults: featureFlagsDefaults
});

Using Toggly Without API (Offline Mode)​

You can use Toggly with only flag defaults, without connecting to the Toggly API:

Toggly.init({
flagDefaults: {
"Feature1": true,
"Feature2": false,
}
});

This is useful for:

  • Development and testing
  • Offline-first applications
  • Prototyping without API setup

Cache Management​

Toggly caches feature flags in localStorage for offline access and performance.

Accessing Cached Flags​

Flags are automatically cached after successful API calls. You can access them via:

const flags = Toggly.featureFlagsValue;

Manual Cache Management​

You can manually manage the cache:

// Cache flags manually
Toggly.cacheFeatureFlags({
"Feature1": true,
"Feature2": false
});

// Clear the cache
Toggly.clearFeatureFlagsCache();

When the cache is cleared, Toggly will use the flag defaults until new flags are fetched.

HTML/CSS Integration​

For static show/hide with no JavaScript, use the dedicated CSS / HTML stylesheet. Link defs.css from client.toggly.io and mark elements with feature-key. That path cannot do per-visitor targeting or percentage rollouts.

This Vanilla SDK fetches JSON from definitions.toggly.io and is the right choice when you need identity, targeting, hooks, or programmatic checks.

Debug Mode​

Enable debug mode to see detailed logging of Toggly operations:

Toggly.init({
appKey: '<YOUR_APP_KEY>',
environment: '<YOUR_APP_ENVIRONMENT>',
isDebug: true
});

When debug mode is enabled, Toggly will log:

  • Feature flag fetches
  • Cache operations
  • Feature gate evaluations
  • Refresh operations

Page Reload on Flag Changes​

You can configure Toggly to automatically reload the page when feature flags change:

Toggly.init({
appKey: '<YOUR_APP_KEY>',
environment: '<YOUR_APP_ENVIRONMENT>',
reloadOnFeatureFlagValidation: true
});

This is useful when you want to ensure users see the latest feature configuration immediately.

Device-local post-filter gates​

Gate bundles of flags behind device-local master switches while keeping rollouts on the worker. See the full guide: Post-filter gates.

let apiRedesignEnabled = false;

await Toggly.init({
appKey: '<YOUR_APP_KEY>',
environment: '<YOUR_APP_ENVIRONMENT>',
localGates: [{
id: 'apiRedesign',
flagKeys: ['ApiV2Checkout', 'ApiV2Profile'],
isEnabled: () => apiRedesignEnabled,
}],
});

apiRedesignEnabled = false;
Toggly.notifyLocalGatesChanged();

Best Practices​

  1. Initialize Once: Initialize Toggly once when your app loads
  2. Use Caching: Feature definitions are automatically cached in localStorage
  3. Handle Errors: The SDK gracefully falls back to cached flags or defaults on errors
  4. Provide User Context: Set identity for accurate targeting and rollouts
  5. Set Flag Defaults: Provide defaults for offline scenarios
  6. Enable Debug Mode: Use isDebug: true during development to troubleshoot issues
  7. Manage Refresh Interval: Adjust featureFlagsRefreshInterval based on your needs (0 to disable)
  8. Clear Cache When Needed: Clear the cache when switching users or testing different configurations

Extensibility with Hooks​

Toggly provides a powerful hooks system that allows you to extend SDK functionality by hooking into feature flag lifecycle events. This is perfect for integrating with analytics platforms like Microsoft Clarity, monitoring tools, or implementing custom behaviors.

What are Hooks?​

Hooks let you execute custom code at specific points in the feature flag evaluation lifecycle:

  • beforeEvaluation: Called before a feature flag is evaluated
  • afterEvaluation: Called after a feature flag is evaluated (with the result)
  • beforeIdentify: Called before user identity is set or cleared
  • afterIdentify: Called after user identity is set or cleared
  • afterRefresh: Called after feature definitions are refreshed from Toggly

Creating a Hook​

A hook is an object that implements the Hook interface from @ops-ai/toggly-hooks-types:

const myAnalyticsHook = {
getMetadata: () => ({
name: 'MyAnalyticsHook',
version: '1.0.0'
}),

beforeEvaluation: async (data) => {
console.log('About to evaluate:', data.featureKey);
},

afterEvaluation: async (data) => {
console.log('Evaluated:', data.featureKey, '=', data.result);
// Send to analytics
if (typeof analytics !== 'undefined') {
analytics.track('Feature Flag Evaluated', {
feature: data.featureKey,
enabled: data.result
});
}
},

afterIdentify: async (data) => {
console.log('Identity set:', data.userId);
}
};

Registering Hooks​

You can register hooks in two ways:

1. During initialization:​

Toggly.init({
appKey: '<YOUR_APP_KEY>',
environment: '<YOUR_APP_ENVIRONMENT>',
hooks: [myAnalyticsHook, myMonitoringHook]
})
.then(function () {
// Hooks are now active
});

2. At runtime:​

// Add a hook
Toggly.addHook(myAnalyticsHook);

// Remove a hook
Toggly.removeHook(myAnalyticsHook.getMetadata().name);

Hook Execution Order​

When multiple hooks are registered:

  • before hooks execute in FIFO order (first registered, first executed)
  • after hooks execute in LIFO order (last registered, first executed)

This creates a "wrap" pattern where the first hook to start is the last to finish.

Error Isolation​

Hooks are designed to be safe:

  • If a hook throws an error, it won't affect feature flag evaluation
  • Other hooks will continue to execute
  • Errors are logged but don't propagate to your application code

Performance​

Hooks are optimized for minimal performance impact:

  • Hooks execute asynchronously without blocking evaluation
  • Hook execution is extremely fast (typically < 1ms per hook)
  • Multiple hooks can be registered without significant overhead

Common Use Cases​

Microsoft Clarity Integration​

const clarityHook = {
getMetadata: () => ({ name: 'Microsoft Clarity', version: '1.0.0' }),
afterEvaluation: async (data) => {
if (typeof clarity !== 'undefined') {
clarity('event', `FeatureFlag:${data.featureKey}`);
}
}
};

Toggly.init({
appKey: '<YOUR_APP_KEY>',
environment: '<YOUR_APP_ENVIRONMENT>',
hooks: [clarityHook]
});

Debug Logging​

const debugHook = {
getMetadata: () => ({ name: 'DebugLogger', version: '1.0.0' }),
beforeEvaluation: async (data) => {
console.debug('[Toggly] Evaluating:', data);
},
afterEvaluation: async (data) => {
console.debug('[Toggly] Result:', data.featureKey, '=', data.result);
}
};

Performance Monitoring​

const performanceHook = {
getMetadata: () => ({ name: 'PerformanceMonitor', version: '1.0.0' }),
beforeEvaluation: async (data) => {
return { startTime: performance.now() };
},
afterEvaluation: async (data) => {
const duration = performance.now() - data.context.startTime;
if (duration > 10) {
console.warn(`Slow evaluation: ${data.featureKey} took ${duration}ms`);
}
}
};

Google Analytics 4 Integration​

const ga4Hook = {
getMetadata: () => ({ name: 'Google Analytics 4', version: '1.0.0' }),
afterEvaluation: async (data) => {
if (typeof gtag !== 'undefined') {
gtag('event', 'feature_flag_evaluated', {
feature_name: data.featureKey,
feature_enabled: data.result,
user_id: data.userId
});
}
}
};

See also​

Next Steps​