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.
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'
});
| Property | Default | Description |
|---|---|---|
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. |
offline | false | Make no network calls and serve defaultFeatures only. For tests and local development. |
application | undefined | Names this site or app so the dashboard can attribute SDK usage and evaluations to it. See Application Tags. |
useCookies | true | Set to false to stop the ff-anonymous-id cookie. You must then pass the anonymous id to your server yourself. |
disableEvents | false | Stop sending impression and goal events while still fetching flags. |
delayInit | false | Return the client without fetching. You must call featureflow.initialise() yourself. |
integrations | [] | Analytics exposure listeners. See A/B testing. |
baseUrl, eventsUrl | Featureflow hosts | Only 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-idcookie server-side. - Different domain: send it yourself, as the
X-Featureflow-Anonymous-Idheader or aff-anonymousidquery parameter, usingfeatureflow.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
- featureflow-javascript-sdk on GitHub, including a runnable example and the changelog
- ReactJS Client for React apps
- Quick Start - AI coding agent to have your agent do the wiring