← All @molecule/* packages · App templates

@molecule/api-feature-flags

Core interface · feature-flags · API (Node) · v1.0.1 · Apache-2.0

Feature flag management core interface for molecule.dev — flag evaluation, targeting rules, and percentage rollouts

npm install @molecule/api-feature-flags

npm · Source on GitHub

How it works

@molecule/api-feature-flags is the feature-flags core interface on the API (Node) side: the API your app calls, with no vendor inside.

Choose the implementation by bonding one of its 1 provider: @molecule/api-feature-flags-database.

import { setProvider, isEnabled, setFlag, evaluateForUser } from '@molecule/api-feature-flags'
import { provider } from '@molecule/api-feature-flags-database'

// Wire the provider at startup
setProvider(provider)

// Create a feature flag with percentage rollout
await setFlag({ name: 'new-dashboard', enabled: true, percentage: 50 })

// Check if a flag is enabled for a user
const enabled = await isEnabled('new-dashboard', { userId: 'user-123' })

// Evaluate all flags for a user
const flags = await evaluateForUser('user-123')

Providers (1): @molecule/api-feature-flags-database

Works with: @molecule/api-bond, @molecule/api-i18n

Reference

Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit src/index.ts JSDoc, not this file.

Feature flags core interface for molecule.dev.

Provides the FeatureFlagProvider interface for feature flag management including flag evaluation, CRUD operations, rule-based targeting, and percentage rollouts. Bond a concrete provider (e.g. @molecule/api-feature-flags-database) at startup via setProvider().

Quick Start

import { setProvider, isEnabled, setFlag, evaluateForUser } from '@molecule/api-feature-flags'
import { provider } from '@molecule/api-feature-flags-database'

// Wire the provider at startup
setProvider(provider)

// Create a feature flag with percentage rollout
await setFlag({ name: 'new-dashboard', enabled: true, percentage: 50 })

// Check if a flag is enabled for a user
const enabled = await isEnabled('new-dashboard', { userId: 'user-123' })

// Evaluate all flags for a user
const flags = await evaluateForUser('user-123')

Type

core

Installation

npm install @molecule/api-feature-flags @molecule/api-bond @molecule/api-i18n

API

Interfaces

FeatureFlag

A feature flag definition.

interface FeatureFlag {
  /** The unique flag name/key. */
  name: string

  /** Whether the flag is globally enabled. */
  enabled: boolean

  /** Human-readable description of the flag's purpose. */
  description?: string

  /** Targeting rules. When present, the flag is enabled only if all rules match. */
  rules?: FlagRule[]

  /** Percentage rollout (0–100). When set, only the given percentage of users see the flag. */
  percentage?: number

  /** When the flag was created. */
  createdAt: Date

  /** When the flag was last updated. */
  updatedAt: Date
}

FeatureFlagProvider

Feature flag provider interface.

All feature flag providers must implement this interface to provide flag evaluation, CRUD operations, rule-based targeting, and percentage rollouts.

interface FeatureFlagProvider {
  /**
   * Checks whether a flag is enabled for the given context.
   * Evaluates targeting rules and percentage rollouts.
   *
   * @param flag - The flag name/key to check.
   * @param context - Optional evaluation context with user and attributes.
   * @returns `true` if the flag is enabled for the given context.
   */
  isEnabled(flag: string, context?: FlagContext): Promise<boolean>

  /**
   * Retrieves a flag definition by name.
   *
   * @param flag - The flag name/key.
   * @returns The flag definition, or `null` if not found.
   */
  getFlag(flag: string): Promise<FeatureFlag | null>

  /**
   * Creates or updates a feature flag.
   *
   * @param flag - The flag data to create or update.
   * @returns The created or updated flag definition.
   */
  setFlag(flag: FeatureFlagUpdate): Promise<FeatureFlag>

  /**
   * Retrieves all feature flags.
   *
   * @returns Array of all flag definitions.
   */
  getAllFlags(): Promise<FeatureFlag[]>

  /**
   * Deletes a feature flag.
   *
   * @param flag - The flag name/key to delete.
   */
  deleteFlag(flag: string): Promise<void>

  /**
   * Evaluates multiple flags for a specific user. Returns a map of
   * flag names to their enabled state.
   *
   * @param userId - The user identifier.
   * @param flags - Optional list of flag names to evaluate. If omitted, evaluates all flags.
   * @returns A record mapping flag names to their enabled state.
   */
  evaluateForUser(userId: string, flags?: string[]): Promise<Record<string, boolean>>
}

FeatureFlagUpdate

Payload for creating or updating a feature flag.

interface FeatureFlagUpdate {
  /** The unique flag name/key. */
  name: string

  /** Whether the flag is globally enabled. */
  enabled: boolean

  /** Human-readable description of the flag's purpose. */
  description?: string

  /** Targeting rules. */
  rules?: FlagRule[]

  /** Percentage rollout (0–100). */
  percentage?: number
}

FlagContext

Evaluation context for feature flag checks. Provides user identity and arbitrary attributes for rule-based targeting.

interface FlagContext {
  /** The user identifier for user-specific targeting. */
  userId?: string

  /** Arbitrary attributes for rule evaluation. */
  attributes?: Record<string, unknown>
}

FlagRule

A targeting rule for a feature flag. Rules are evaluated against the provided context to determine if a flag is enabled for a specific user or request.

interface FlagRule {
  /** The context attribute to evaluate (e.g. 'plan', 'country'). */
  attribute: string

  /** The comparison operator. */
  operator: FlagOperator

  /** The value to compare against. */
  value: unknown
}

Types

FlagOperator

Comparison operators for flag targeting rules.

type FlagOperator = 'eq' | 'neq' | 'in' | 'notIn' | 'gt' | 'lt'

Functions

deleteFlag(flag)

Deletes a feature flag using the bonded provider.

function deleteFlag(flag: string): Promise<void>
  • flag — The flag name/key to delete.

Returns: Resolves when the flag is removed.

evaluateForUser(userId, flags)

Evaluates multiple flags for a specific user using the bonded provider.

function evaluateForUser(userId: string, flags?: string[]): Promise<Record<string, boolean>>
  • userId — The user identifier.
  • flags — Optional list of flag names to evaluate.

Returns: A record mapping flag names to their enabled state.

getAllFlags()

Retrieves all feature flags using the bonded provider.

function getAllFlags(): Promise<FeatureFlag[]>

Returns: Array of all flag definitions.

getFlag(flag)

Retrieves a flag definition by name using the bonded provider.

function getFlag(flag: string): Promise<FeatureFlag | null>
  • flag — The flag name/key.

Returns: The flag definition, or null if not found.

getProvider()

Retrieves the bonded feature flag provider, throwing if none is configured.

function getProvider(): FeatureFlagProvider

Returns: The bonded feature flag provider.

hasProvider()

Checks whether a feature flag provider is currently bonded.

function hasProvider(): boolean

Returns: true if a feature flag provider is bonded.

isEnabled(flag, context)

Checks whether a flag is enabled for the given context using the bonded provider.

function isEnabled(flag: string, context?: FlagContext): Promise<boolean>
  • flag — The flag name/key to check.
  • context — Optional evaluation context.

Returns: true if the flag is enabled for the given context.

setFlag(flag)

Creates or updates a feature flag using the bonded provider.

function setFlag(flag: FeatureFlagUpdate): Promise<FeatureFlag>
  • flag — The flag data to create or update.

Returns: The created or updated flag definition.

setProvider(provider)

Registers a feature flag provider as the active singleton. Called by bond packages during application startup.

function setProvider(provider: FeatureFlagProvider): void
  • provider — The feature flag provider implementation to bond.

Available Providers

ProviderPackage
Feature Flags@molecule/api-feature-flags-database

Injection Notes

Requirements

Peer dependencies:

  • @molecule/api-bond ^1.0.1
  • @molecule/api-i18n ^1.0.1

Runtime Dependencies

  • @molecule/api-bond

  • @molecule/api-i18n

  • Percentage/rule targeting only applies when you pass context. isEnabled('new-dashboard') with NO { userId } falls back to the bare global toggle — for a percentage flag that means EVERYONE gets it. Always pass the authenticated user's id for user-facing gating.

  • Evaluate flags SERVER-SIDE and ship booleans. Expose an endpoint that returns evaluateForUser(userId) results for the UI to consume — never send raw flag definitions/rules to the client or re-implement targeting there.

  • A client-side flag gate is UX, not security. Anything the flag protects must also be gated on the API route with the same check.

  • Rollouts must be sticky per user. Evaluate with the same stable userId every time — a session id (or none) makes features flicker between requests.

  • setFlag() is an upsert keyed on name. Flag CRUD (setFlag, deleteFlag, getAllFlags) is an admin surface — put it behind an admin authorizer, not a public route.

E2E Tests

Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual screens/flows, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:

  • A flag actually GATES behavior: with the flag OFF the feature it guards is hidden/disabled in the UI; flip it ON from the admin screen (or setFlag) and reload — the feature appears with no code change or rebuild. Turn it back OFF and it disappears again.
  • Targeting evaluates PER USER: for a rule- or percentage-flag, a user inside the segment (matching attributes/rollout) sees the feature and a user outside it does not — verify by signing in as each and via isEnabled(flag, { userId, attributes }) / evaluateForUser(userId) returning the right boolean for each. The same user's result is sticky across reloads, not flickering between requests.
  • An UNDEFINED flag (never created) evaluates to the SAFE default: isEnabled('does-not-exist', ctx) returns false and the guarded feature stays hidden — it does NOT throw or fall open.
  • Flag reads are cheap on the hot path — evaluation is server-side and cached, not a DB round-trip per render — and a toggle propagates promptly (within a reload / short cache window), not only after a restart.
  • The gate is enforced SERVER-SIDE, not just in the UI: calling the API route the flag protects with the flag OFF is rejected, not merely hidden (a client-side flag gate is UX, not security).
  • ADMIN-ONLY writes: only an authorized admin can create/toggle/delete flags. A normal signed-in user hitting the flag-CRUD endpoints (setFlag/deleteFlag/getAllFlags) is rejected — they can't flip a flag or read raw flag definitions/rules through any exposed route.