Add Feature Flags
Add feature flags and A/B testing to your Terreno app using @terreno/feature-flags.
Prerequisites
- A working Terreno backend using
TerrenoApp @terreno/admin-backendregistered for the admin panel (optional but recommended)
Steps
1. Install the package
bun add @terreno/feature-flags
2. Register the backend plugin
In your server file, register FeatureFlagsApp and add the admin config:
import {TerrenoApp} from "@terreno/api";
import {AdminApp} from "@terreno/admin-backend";
import {FeatureFlagsApp, featureFlagAdminConfig} from "@terreno/feature-flags";
// Define segment functions to classify users for targeting rules
const segments = {
"pro-users": (user) => user.plan === "pro",
"beta-testers": (user) => user.betaTester === true,
"internal-team": (user) => user.email?.endsWith("@mycompany.com"),
};
new TerrenoApp({userModel: User})
.register(new FeatureFlagsApp({segments}))
.register(
new AdminApp({
models: [
featureFlagAdminConfig, // adds Feature Flags to the admin panel
// ...your other models
],
})
)
.start();
featureFlagAdminConfig is a pre-configured admin model config exported by the package. It sets up the display name, list fields, route path, and model reference so you don't need to configure them manually.
3. Regenerate the frontend SDK
After adding the backend routes, regenerate the SDK so the frontend can call the new endpoints (with the example backend running, or set OPENAPI_URL to your deployed openapi.json):
cd your-frontend && bun run sdk
Regeneration may emit a hook for /feature-flags/flagConfiguration; prefer useTerrenoFeatureFlags / useFeatureFlags from @terreno/rtk so OpenFeature domain, cache keying, and optional socket refetch stay consistent.
4. Use feature flags in the frontend
Recommended (OpenFeature): add @openfeature/react-sdk to the app, wrap the tree (or the subtree that reads flags) in <OpenFeatureProvider domain="feature-flags">, and call useTerrenoFeatureFlags(terrenoApi, {userId, socket}) once after auth so the OpenFeature client receives GET /feature-flags/flagConfiguration. Then use standard hooks such as useBooleanFlagValue / useStringFlagValue / FeatureFlag.
Back-compat: useFeatureFlags(terrenoApi) from @terreno/rtk still returns {flags, getFlag, getVariant, isLoading, …} with the same semantics as before; it uses /flagConfiguration under the hood. You do not need to call a generated RTK hook for /flagConfiguration directly.
import {useBooleanFlagValue} from "@openfeature/react-sdk";
import {useTerrenoFeatureFlags, useSelectCurrentUserId} from "@terreno/rtk";
import {terrenoApi} from "@/store/sdk";
const GatedBanner: React.FC = () => {
const userId = useSelectCurrentUserId();
useTerrenoFeatureFlags(terrenoApi, {userId, skip: !userId});
const showBanner = useBooleanFlagValue("new-ui", false);
return showBanner ? <NewUI /> : null;
};
import {useFeatureFlags} from "@terreno/rtk";
import {terrenoApi} from "@/store/sdk";
const LegacyStyle: React.FC = () => {
const {getFlag, isLoading} = useFeatureFlags(terrenoApi);
if (isLoading) {
return <Spinner />;
}
const showNewUI = getFlag("new-ui");
return showNewUI ? <NewUI /> : <OldUI />;
};
4b. Live updates (optional)
To push flag changes to open clients without a refresh, pass liveUpdates when constructing FeatureFlagsApp (see example-backend/src/server.ts): Mongoose FeatureFlag.watch() drives a Socket.io broadcast. MongoDB must run as a replica set (a single-node replica set is enough); standalone instances cannot use change streams, and the plugin logs a warning if watch() fails.
On the client, pass the same Socket.io client into useTerrenoFeatureFlags / useFeatureFlags as socket; the hook listens for featureFlagsChanged (or socketEventName) and refetches /flagConfiguration. The event payload is only { key: string } — the same flag keys are already returned to every authenticated user in /flagConfiguration, so this is not considered a new exposure.
5. Create flags via the admin panel
Navigate to the admin panel in your app. The "Feature Flags" card appears automatically. Create a flag with:
- Key: unique identifier, e.g.
new-ui - Name: human-readable label
- Type:
booleanfor on/off toggles,variantfor A/B tests - Enabled: global kill switch
- Rules: optional targeting (field-based or segment-based)
- Rollout Percentage: gradual rollout for boolean flags (default 100%)
Next steps
- See the reference docs for the full API, targeting rules, and variant flags
- See
example-backend/src/server.tsfor a complete working example