Skip to main content
Feature flags give backend and portal code a single tools.featureFlags contract while PostHog, LaunchDarkly, or a local dev stub handle evaluation behind the scenes.

Why

Feature flags belong in infrastructure, not scattered if (process.env…) checks. Refract normalizes every provider into the same evaluation model (enabled + optional variant), adds request-level consistency for GraphQL, and optionally shares results across requests via cacheMs.

Public API

preload() is transparent — isEnabled, getVariant, and getAll call it automatically. Multiple reads in the same HTTP request hit the provider at most once when bulk caching succeeds.

Configuration

  • cacheMs: 0 disables the shared cache; request caching remains enabled.
  • exposedFlags controls which keys appear on me.featureFlags.

Provider contract

Remote packages implement FeatureFlagProviderType: the same surface as FeatureFlagTool except preload (the backend cache wrapper owns preload). LOCAL returns a full FeatureFlagTool directly (no cache wrapper). PostHog and LaunchDarkly return a provider; buildCachedFeatureFlagTool in the loader adds preload plus L1/L2 caching. When adding a provider, implement all three evaluation methods. Bulk-first vendors should make getAll efficient; per-flag-only vendors can no-op getAll to {} and rely on fall-through (see below).

Caching

Resolution order per evaluation context key (user:123|org:456, anonymous, etc.):

Bulk path (preload / getAll)

  1. Request cache (always on for HTTP traffic via AsyncLocalStorage)
  2. Shared Redis cache (tools.cache.helpers.featureFlagEvaluation, TTL from cacheMs) — accessed through the FeatureFlagSharedCache port
  3. Provider getAll (vendor bulk API)

Per-flag path (isEnabled / getVariant)

  1. Same request + shared bulk snapshot when the key is present in the cached map (including enabled: false)
  2. Fall-through to provider isEnabled / getVariant when:
    • the key is absent from the bulk snapshot (e.g. provider returned {}, or bulk omitted that flag)
    • bulk population failed (errors are swallowed; per-flag calls still work)
Fall-through does not run when the bulk snapshot explicitly contains the key — a cached { enabled: false } is authoritative. The L2 helper is registered in apps/backend/src/tools/cache/featureFlagEvaluation.ts and wired when buildFeatureFlags runs with tools.cache.

GraphQL

Authenticated clients can read frontend-safe flags from me.featureFlags. Internal flags stay server-side and are accessed only through tools.featureFlags. Evaluation context is built from the authenticated user (userId, current organization from membership). If user.memberships is not hydrated, the utility loads them from the database before resolving flags.

Providers

What’s next?