Skip to main content

Feature Toggles

Overview

[!IMPORTANT] Required Role: Super Admin only.

The feature_toggles module allows administrators to enable or disable features, modules, or specific functionality at runtime without code deployment. It also supports runtime configuration, allowing you to change application behavior (strings, numbers, JSON config) dynamically. This provides operational flexibility for controlled rollouts, client-specific customizations, and emergency kill switches.

Key Capabilities

  • Hierarchical Resolution: Resolves flags from specific tenant overrides up to global defaults.
  • High Performance: Uses aggressive caching with instant invalidation to minimize latency.
  • Audit Trails: Tracks all changes to toggles and overrides, supporting undo operations.
  • Context Aware: Designed to work seamlessly with multi-tenant architectures.

Data Model

The feature toggles system is built around two primary entities that control feature availability.

FeatureToggle

Defines the global state and behavior of a feature flag.

  • id (uuid): Unique identifier for the toggle.
  • identifier (string): The unique key used in code to check the feature (e.g., checkout.new_flow).
  • name (string): Human-readable name.
  • description (string?): Optional description of the feature.
  • category (string?): Optional grouping category.
  • type (enum): The data type of the toggle (boolean, string, number, json).
  • defaultValue (jsonb): The global default value used if no specific override matches.

FeatureToggleOverride

Allows granular control over a feature toggle for specific contexts.

  • id (uuid): Unique identifier for the override.
  • toggle (relation): Reference to the parent FeatureToggle.
  • tenantId (uuid): The tenant this override applies to.
  • value (jsonb): The specific value for this context.

If no FeatureToggleOverride entity exists for a specific tenant, the system inherits the value from the FeatureToggle.defaultValue.

Caching Strategy

To ensure high performance and minimal latency, feature flag resolutions are heavily cached.

Resolution Flow

  1. Cache Check: The system checks for a cached result using the key feature_toggles:isEnabled:{identifier}:{tenantId}.
  2. Database Lookup (Toggle): If not cached, it fetches the FeatureToggle definition from the database.
  3. Database Lookup (Override): It then checks for a FeatureToggleOverride matching the requested tenant.
  4. Result Construction: The final state is resolved (Override > Default State) and cached.

Cache Tags & Invalidation

Cache entries are tagged to allow for precise invalidation when toggles or overrides are modified.

  • feature_toggles:toggle:{identifier}
  • feature_toggles:tenant:{tenantId}

Audit Logs

All modifications to feature toggles and overrides are audited to ensure accountability and traceability.

  • Action Logs: Creation, updates, deletions, and state changes are logged. The system supports undo operations for these actions, allowing you to revert changes quickly.
  • Access Logs: Read access to toggle overrides via the API is logged (read:list) to track who is verifying feature configurations.

Usage Reference

The system supports strict typing for different feature toggle kinds. Choose the appropriate method for your data type.

Boolean Flags

Use for simple on/off switches.

React Component (FeatureGuard)

Wrapper for conditional rendering. Designed strictly for boolean toggles.

import { FeatureGuard } from '@saasframe/core/modules/feature_toggles/components/FeatureGuard';

<FeatureGuard
id="checkout_flow_v2"
fallback={<div className="alert">Feature disabled</div>}
>
<CheckoutComponent />
</FeatureGuard>

Frontend Hook

const { enabled, isLoading } = useFeatureFlagBoolean({ id: 'checkout_flow_v2' });

Backend Service

const result = await service.getBoolConfig('checkout_flow_v2', tenantId);
if (result.ok && result.value) {
// Feature is enabled
}

String Configuration

Use for dynamic text, labels, or string keys.

Frontend Hook

const { value: title } = useFeatureFlagString({ id: 'home_page_title' });

Backend Service

const result = await service.getStringConfig('home_page_title', tenantId);

Number Configuration

Use for numeric limits, timeouts, or counts.

Frontend Hook

const { value: limit } = useFeatureFlagNumber({ id: 'max_items_per_order' });

Backend Service

const result = await service.getNumberConfig('max_items_per_order', tenantId);

JSON Configuration

Use for complex configuration objects.

Frontend Hook

// Pass a generic type for type safety
const { value: config } = useFeatureFlagJson<PaymentConfig>({ id: 'payment_provider_config' });

Backend Service

const result = await service.getJsonConfig<PaymentConfig>('payment_provider_config', tenantId);

Error Handling

All check operations return a standardized Result<T> structure (backend) or expose error states (frontend).

Result Structure

type Result<T> =
| { ok: true; value: T; resolution: ResolutionMetadata }
| { ok: false; error: ToggleError; resolution: ResolutionMetadata }

Error Codes

  • TYPE_MISMATCH: The toggle exists but is not of the requested type (e.g., requested String but found Boolean).

  • MISSING_TOGGLE: The identifier does not exist in the system.

  • INVALID_VALUE: The stored value could not be parsed.

API Status Codes

When interacting with the API directly:

  • 200 OK: Successful check.
  • 400 Bad Request: Type mismatch or missing parameters.
  • 401 Unauthorized: Missing authentication.
  • 404 Not Found: Toggle identifier does not exist or tenant context is missing.

Managing Feature Flags in the Admin

Manage your feature toggles through the Admin UI.

Global Feature Toggles

View and manage all global feature flags.

Global feature toggles view

Editing a Feature Toggle

Update the status and details of a feature toggle.

Edit feature toggle

Overrides

Configure specific overrides for feature toggles.

Overrides in particular feature toggles