Skip to content

Feature Flags (Tenant-Level)

Feature flags are per-tenant boolean toggles stored in the database. They control visibility of features on both frontend and backend without code deploys.

How It Works

Database

Feature flags live in the TenantSettings table in the features JSON column:

{
  "rtmBillingDashboard": true,
  "allActivePatientsDashboard": true,
  "therapistPatientAssignment": true
}

To enable a feature for a tenant, update (or insert) the JSON directly:

UPDATE "TenantSettings"
SET features = jsonb_set(COALESCE(features, '{}'), '{myNewFeature}', 'true')
WHERE "tenantId" = <TENANT_ID>;

Backend

Reading flags: Use prisma.tenantSettings.findUnique({ where: { tenantId } }) and cast .features to access individual keys.

Endpoint: GET /v2/tenant/features (authenticated) returns all flags for the requesting user's tenant:

{ "features": { "allActivePatientsDashboard": true } }

Guarding an endpoint: Add a check at the top of any controller that should be restricted:

const settings = await prisma.tenantSettings.findUnique({
  where: { tenantId },
});
const features = (settings?.features as any) || {};
if (features.myNewFeature !== true) {
  res.status(403).json({ error: "Feature not enabled for this tenant" });
  return;
}

See OktaPT-API/src/routes/doctor/controller.ts (getAllTenantPatients) for a working example.

Frontend

Store: OktaPT-FE/lib/stores/tenantFeatures.ts is a Zustand store that fetches and caches flags.

import { useTenantFeaturesStore } from "@/lib/stores/tenantFeatures";

// Inside a component:
const { hasFeature, fetchFeatures } = useTenantFeaturesStore();
const isEnabled = hasFeature("myNewFeature");

Initialization: fetchFeatures() is called once during dashboard init (in pages/doctor/dashboard.tsx). It caches the result so subsequent calls are no-ops. If your feature is on a page that doesn't go through the dashboard, call fetchFeatures() in that page's init useEffect.

Conditional rendering:

{
  hasFeature("myNewFeature") && <MyFeatureComponent />;
}

Adding a New Feature Flag

  1. Pick a name. Use camelCase (e.g., myNewFeature).
  2. Backend guard. If the feature has an API endpoint, add the guard pattern shown above to the controller.
  3. Frontend guard. Use hasFeature("myNewFeature") to conditionally render UI.
  4. Enable per tenant. Set the flag to true in the tenant's TenantSettings.features JSON.

No schema migration is needed -- the features column is a JSON field that accepts arbitrary keys.

Examples ofExisting Feature Flags

Flag Purpose
rtmBillingDashboard Enables RTM billing dashboard tab and endpoints
allActivePatientsDashboard Shows the "All Active Patients" sub-tab in the doctor dashboard
therapistPatientAssignment Enables multi-therapist assignment for patients (at invite and from dashboard)
therapistPersonalizedExerciseVideo Enables personalized exercise video recording and playback for doctors
exerciseAutoCanonicalization Enables automatic AI-powered duplicate exercise detection and merging
therapistExerciseManagement Enables the Exercises tab for doctors on mobile (browse, create, edit, filter exercises)
therapistMobileHepManagement Enables mobile HEP management — doctors can view and edit patient workout plans from the mobile app

Mobile

The mobile app has its own feature flag system using a React context provider.

File: Okta-Mobile/context/TenantFeaturesContext.tsx

import { useTenantFeatures } from "@/context/TenantFeaturesContext";

// Inside a component:
const { hasFeature } = useTenantFeatures();
const isEnabled = hasFeature("therapistPersonalizedExerciseVideo");

How it works: - TenantFeaturesProvider wraps the root stack in app/_layout.tsx (inside AuthProvider) - Fetches features from GET /v2/tenant/features when the user is authenticated - Re-fetches when the app returns to the foreground (via AppState listener) - Exposes hasFeature(name: string): boolean

File: Okta-Mobile/services/tenantFeatureService.tsfetchTenantFeatures() API call using httpClient.

Key Files

File Role
OktaPT-API/src/routes/tenant/controller.ts getTenantFeatures controller
OktaPT-API/src/routes/tenant/index.ts GET /features route
OktaPT-FE/lib/stores/tenantFeatures.ts Zustand store (fetch + cache + hasFeature)
Okta-Mobile/context/TenantFeaturesContext.tsx React context provider (hasFeature)
Okta-Mobile/services/tenantFeatureService.ts Mobile API service for fetching flags