Reference
TypeScript, React and Angular SDK
Use @forgeops/fluxgate in Node.js and browsers, with React hooks or Angular signals on top, and keep every flag for a user in memory.
Before you start
- Node.js 20 or newer. React 18 or newer, or Angular 19 or newer, for the adapters.
- The edge server is running and
express-checkoutis deployed to Development. See Clients and edge server. - For Node.js, the SDK key of a Backend client:
checkout-service. For React and Angular, the SDK key of a Web client whose Web Origins list your app's origin:juniper-web.
Packages
| Package | Use it for | Links |
|---|---|---|
@forgeops/fluxgate | Node.js and browsers. FluxGateClient (retries, timeouts, cache), FlagSnapshot for synchronous reads with polling, and OpenFeature providers for the server and web SDKs. No dependencies. | npm |
@forgeops/fluxgate-react | React 18 and 19. FluxGateProvider and the hooks useFlag, useFlagDetails, useIsEnabled, useFluxGateStatus. | npm |
@forgeops/fluxgate-angular | Angular 19 and later, with or without zone.js. provideFluxGate(), FluxGateService with signals, and the *fgFlag directive. | npm |
Source code, samples and the full reference: forgeopslabs/fluxgate-js on GitHub. The current version is 1.0.2. Every package includes TypeScript types.
Node.js
npm install @forgeops/fluxgateimport { FluxGateClient } from '@forgeops/fluxgate';
const fluxgate = new FluxGateClient({
baseUrl: 'http://localhost:8081',
sdkKey: process.env.FLUXGATE_SDK_KEY!, // <clientId>.<apiKey>
});
const user = { targetingKey: 'user-1', country: 'CA', user_tier: 'plus' };
const variant = await fluxgate.getString('express-checkout', 'classic', user);
console.log(`checkout: ${variant}`);
const details = await fluxgate.evaluate('express-checkout', 'classic', user);
console.log(details);checkout: express
{
key: 'express-checkout',
reason: 'TARGETING_MATCH',
value: 'express',
variant: 'express'
}| Method | Returns |
|---|---|
isEnabled(key, context) | true only when a boolean flag is on |
getBoolean, getString, getNumber, getObject | The value, or your default |
evaluate(key, default, context) | FlagEvaluation: value, variant, reason, errorCode, errorMessage, metadata |
evaluateAll(context, version?) | Every flag in one call; throws FluxGateError on failure |
watch(context, { pollIntervalMs }) | A FlagSnapshot with every flag for one context, kept current by polling |
isHealthy() | true when the edge answers GET /health with 2xx |
| Option | Default |
|---|---|
timeoutMs | 5000 |
retry.maxAttempts, retry.initialBackoffMs | 3, 100 |
cache.enabled, cache.ttlMs, cache.maxSize | true, 30000, 1000 |
fetch | The global fetch |
React
npm install @forgeops/fluxgate @forgeops/fluxgate-reactimport { FluxGateProvider, useFlag, useFluxGateStatus, useIsEnabled } from '@forgeops/fluxgate-react';
const options = {
baseUrl: 'http://localhost:8081',
sdkKey: import.meta.env.VITE_FLUXGATE_SDK_KEY, // the juniper-web SDK key
};
export function App({ user }: { user: { id: string; country: string; tier: string } }) {
return (
<FluxGateProvider options={options} context={{ targetingKey: user.id, country: user.country, user_tier: user.tier }}>
<Checkout />
</FluxGateProvider>
);
}
function Checkout() {
const { ready } = useFluxGateStatus();
const checkout = useFlag('express-checkout', 'classic');
const banner = useIsEnabled('holiday-banner');
if (!ready) return <p>Loading…</p>;
return (
<>
{banner && <HolidayBanner />}
{checkout === 'express' ? <ExpressCheckout /> : <ClassicCheckout />}
</>
);
}The provider loads every flag for the context in one bulk call and polls every 30 seconds (pollIntervalMs, 0 turns polling off). When the context changes by value, for example after sign-in, it loads the flags again. Hooks read from memory, and components re-render only when a flag or the status changes. The provider works in StrictMode.
| Hook | Returns |
|---|---|
useFlag(key, default) | The value. The default is returned while loading and on errors. A string default gives a string; to type the variants, pass them: useFlag<'classic' | 'express'>(...). |
useFlagDetails(key, default) | The full FlagEvaluation. |
useIsEnabled(key) | true only when a boolean flag is on. |
useFluxGateStatus() | { status, ready, error }. status is NOT_READY, READY, STALE or ERROR. |
useFluxGateClient() | The FluxGateClient, for evaluations on demand. |
Angular
npm install @forgeops/fluxgate @forgeops/fluxgate-angularimport { bootstrapApplication } from '@angular/platform-browser';
import { provideFluxGate } from '@forgeops/fluxgate-angular';
bootstrapApplication(AppComponent, {
providers: [
provideFluxGate({
options: { baseUrl: 'http://localhost:8081', sdkKey: environment.fluxgateSdkKey }, // the juniper-web SDK key
context: { targetingKey: 'user-1', country: 'CA', user_tier: 'plus' },
}),
],
});import { Component, inject } from '@angular/core';
import { FluxGateFlagDirective, FluxGateService } from '@forgeops/fluxgate-angular';
@Component({
selector: 'app-checkout',
imports: [FluxGateFlagDirective],
template: `
<app-holiday-banner *fgFlag="'holiday-banner'" />
@if (checkout() === 'express') {
<app-express-checkout />
} @else {
<app-classic-checkout />
}
`,
})
export class CheckoutComponent {
private readonly fluxgate = inject(FluxGateService);
readonly checkout = this.fluxgate.flag('express-checkout', 'classic');
}By default, the application waits for the first load before it starts (waitForReady: true), so templates never show the defaults first. FluxGateService also offers details(key, default), isEnabled(key), the status, ready and error signals, and setContext(context) to switch users after sign-in.
OpenFeature
The core package includes OpenFeature providers. Install the OpenFeature SDK you need next to it:
import { OpenFeature } from '@openfeature/server-sdk';
import { FluxGateServerProvider } from '@forgeops/fluxgate/openfeature/server';
await OpenFeature.setProviderAndWait(
new FluxGateServerProvider({ baseUrl: 'http://localhost:8081', sdkKey: process.env.FLUXGATE_SDK_KEY! }),
);
const variant = await OpenFeature.getClient().getStringValue('express-checkout', 'classic', {
targetingKey: 'user-1',
country: 'CA',
user_tier: 'plus',
});import { OpenFeature } from '@openfeature/web-sdk';
import { FluxGateWebProvider } from '@forgeops/fluxgate/openfeature/web';
await OpenFeature.setContext({ targetingKey: 'user-1', country: 'CA', user_tier: 'plus' });
await OpenFeature.setProviderAndWait(
new FluxGateWebProvider({ baseUrl: 'http://localhost:8081', sdkKey: '<juniper-web SDK key>' }),
);
const variant = OpenFeature.getClient().getStringValue('express-checkout', 'classic');The web provider loads every flag in one call, reloads when you call OpenFeature.setContext, and emits ConfigurationChanged with the changed keys and Stale while it polls. getObjectValue accepts a flag of any type.
Good to know
- Evaluations never throw.
errorCodeis one ofFLAG_NOT_FOUND,TYPE_MISMATCH,TARGETING_KEY_MISSING,INVALID_CONTEXT,PROVIDER_NOT_READY,PARSE_ERROR,UNAUTHORIZEDandGENERAL. - The default sets the expected type.
nullaccepts any type. - A
FlagSnapshot(React, Angular, the web provider) polls withIf-None-Match, so an unchanged flag set costs a304with no body. When a refresh fails, the snapshot keeps the last flags and reportsSTALE. - A browser bundle ships its SDK key to every visitor. Use a Web client there and never a Backend client. The edge serves a Web client only to the origins in its Web Origins, and answers other origins with
403. - In browsers, use the signed-in user's ID as the targeting key, or a random ID stored on the device for anonymous visitors, so a visitor keeps the same variant.
Dateattributes in a context are sent as ISO 8601 strings.
Try the samples
The repository has a Node.js sample, a React app and an Angular app. samples/docker-compose.yml starts a separate FluxGate stack on ports 28000 (admin UI), 28080 (backend) and 28081 (edge), and samples/setup.sh creates a Backend client, a Web client, a boolean flag and a contextual flag. Follow samples/README.md.
Next
For the other languages, see Java and Spring Boot and Rust. The HTTP calls behind every SDK are in the Edge server API.