Skip to main content
Version: 2.0.0

Javascript Client

Github: https://github.com/featureflow/featureflow-javascript-sdk

featureflow-client is the Featureflow SDK for code that runs in a browser. It fetches already-evaluated flag values for the current user, caches them in localStorage, and reports impressions and goals back to Featureflow.

Uses a client SDK key (sdk-js-env-…), which is public by design. Find it on the Projects page by clicking the key icon next to your environment.

note

Version 2.x targets modern browsers and evaluates flags for one user at a time. For React use the ReactJS Client; for Node.js servers use the Node.js SDK. If you need to support legacy browsers, use the 1.3.18 client.

Installation​

npm install --save featureflow-client
import Featureflow from 'featureflow-client';

Or, without a bundler, load the UMD build from a CDN. It exposes a global Featureflow:

<script crossorigin="anonymous" src="https://unpkg.com/featureflow-client@2/dist/featureflow.umd.min.js"></script>

Getting started​

1. Initialise one client​

init returns a promise that resolves once the first evaluation has landed, so awaiting it before you render flag-driven UI avoids a flash of the wrong variant. Create the client once, keep it in a module, and import it wherever you need it. A second init on the same page means duplicate polling and duplicate impression events.

import Featureflow from 'featureflow-client';

const user = {
id: 'user-123',
attributes: {
tier: 'gold',
country: 'australia',
roles: ['beta']
}
};

const featureflow = await Featureflow.init('sdk-js-env-YOUR_KEY', user);

user is optional. Without it the SDK generates an anonymous id, keeps it in localStorage and, unless useCookies is false, sets an ff-anonymous-id cookie. The id is what percentage rollouts bucket on, so use your own account id for signed-in users.

2. Evaluate a flag​

Evaluation is synchronous against values the server has already computed for this user:

const checkout = featureflow.evaluate('new-checkout');

if (checkout.isOn()) {
showNewCheckout();
} else {
showLegacyCheckout();
}

Multivariate flags use is(variant) or value():

const layout = featureflow.evaluate('pricing-layout');

if (layout.is('compact')) renderCompact();
console.log(layout.value()); // 'compact'

value() falls back to config.defaultFeatures[key], then 'off'. getFeatures() returns every evaluated flag as { key: variant }.

3. Update the user on login and logout​

updateUser re-evaluates every flag for the new user and resolves when done. Call it on login, on logout, and whenever an attribute used in targeting changes:

await featureflow.updateUser({
id: loggedInUser.id,
attributes: { tier: loggedInUser.tier, country: loggedInUser.country }
});

// On logout, back to an anonymous user:
await featureflow.updateUser();

TypeScript​

The package ships its own types:

import { init, events } from 'featureflow-client';
import type { FeatureflowClient, FeatureflowUser, Config, EvaluatedFeatures } from 'featureflow-client';

const featureflow: FeatureflowClient = await init('sdk-js-env-YOUR_KEY', user);

Configuration​

Pass config as the third argument to init (or the second, if you have no user). Everything is optional:

const featureflow = await Featureflow.init('sdk-js-env-YOUR_KEY', user, {
defaultFeatures: { 'new-checkout': 'off', 'kill-switch-payments': 'on' },
application: 'web-app'
});
PropertyDefaultDescription
defaultFeatures{}Variants to serve when Featureflow cannot be reached and nothing is cached. Anything unlisted is off. Set it for any flag whose wrong-way default would be harmful.
offlinefalseMake no network calls and serve defaultFeatures only. For tests and local development.
applicationundefinedNames this site or app so the dashboard can attribute SDK usage and evaluations to it. See Application Tags.
useCookiestrueSet to false to stop the ff-anonymous-id cookie. You must then pass the anonymous id to your server yourself.
disableEventsfalseStop sending impression and goal events while still fetching flags.
delayInitfalseReturn the client without fetching. You must call featureflow.initialise() yourself.
integrations[]Analytics exposure listeners. See A/B testing.
baseUrl, eventsUrlFeatureflow hostsOnly change for self-hosted or proxied setups.

JSON configuration values​

Requires featureflow-client >= 2.2.0 — see SDK Compatibility.

If a variant has a JSON config value set in the dashboard, jsonValue() returns it. It is undefined when the variant has none, so always provide a fallback:

interface ThemeConfig {
color: string;
layout: string;
}

const theme = featureflow.evaluate('checkout-theme').jsonValue<ThemeConfig>();
applyTheme(theme?.color ?? '#000000');

jsonValue() never changes what value(), is(), isOn() or isOff() return.

Goals and experiments​

Track a goal against the current user. The optional second argument is a number (the metric value) or an object whose optional value is the metric value and whose other properties are sent as custom data:

featureflow.track('signup');
featureflow.track('purchase', 49.95);
featureflow.track('purchase', { value: 49.95, plan: 'pro' });

Fire the goal where the conversion happens, and for every variant including the control. featureflow.goal(key) still works but is deprecated. To analyse experiments in your own analytics tool instead, see A/B testing for the Amplitude, Mixpanel, PostHog, Segment and Google Analytics integrations.

Events​

Subscribe with on and unsubscribe with off. Always unsubscribe when a component is torn down.

import { events } from 'featureflow-client';

featureflow.on(events.INIT, (features) => {
// Flags evaluated and loaded. Fires after init and after every updateUser.
});
featureflow.on(events.LOADED_FROM_CACHE, (features) => {
// Cached values from localStorage, available before the network responds.
});
featureflow.on(events.ERROR, (error) => {
// The fetch failed; cached or default values are being served.
});

featureflow.hasReceivedInitialResponse() reports whether the first server response has arrived. events.LOADED is a deprecated alias of INIT.

Sharing an anonymous user with your server​

For an experiment that spans browser and server code, both sides must evaluate the same user id or they will disagree about which variant the user is in. For signed-in users that is your account id. For anonymous users, share the SDK's id:

  • Same domain: read the ff-anonymous-id cookie server-side.
  • Different domain: send it yourself, as the X-Featureflow-Anonymous-Id header or a ff-anonymousid query parameter, using featureflow.getAnonymousId().

resetAnonymousId() issues a new id, for example on logout so the signed-out user leaves the account's buckets. It does not re-evaluate, so follow it with updateUser().

Testing​

Run offline with fixed variants. No network calls, deterministic results, and both branches are testable by changing defaultFeatures:

const featureflow = await Featureflow.init('test', {
offline: true,
defaultFeatures: { 'new-checkout': 'on' }
});

Write a test for each branch. An untested off branch is the usual reason a rollout gets reverted.

Further reading​