Skip to content

Quick Start

A flag is a plain FlagDefinition object — there’s no builder function, the schema fields are the API:

import type { FlagDefinition } from '@useoptimus/core';
const showNewNav: FlagDefinition<boolean> = {
key: 'show-new-nav',
kind: 'release',
valueType: 'boolean',
defaultValue: false,
failureMode: 'closed',
sticky: false,
emitsExposure: false,
};

See Flag Taxonomy for what each field means.

import { evaluate } from '@useoptimus/core';
const result = evaluate(showNewNav, undefined, { userId: 'user_123' });
// { key: 'show-new-nav', value: false, reason: 'default', stale: false, variantKey: undefined }

evaluate takes the flag definition, optional remote state (undefined means “no remote state available” — the definition’s defaultValue and failureMode govern the outcome), and an EvaluationContext carrying the bucketing key.

Remote state overrides the schema field-by-field. Turning the flag on without redeploying:

import type { FlagRemoteState } from '@useoptimus/core';
const remoteState: FlagRemoteState = {
key: 'show-new-nav',
enabled: true,
updatedAt: new Date().toISOString(),
};
evaluate(showNewNav, remoteState, { userId: 'user_123' });
// { key: 'show-new-nav', value: true, reason: 'override', ... }

LocalProvider is the in-memory/static provider — useful for tests and local dev before wiring up a real network provider:

import { LocalProvider, evaluate } from '@useoptimus/core';
const provider = new LocalProvider([
{ key: 'show-new-nav', enabled: true, updatedAt: new Date().toISOString() },
]);
await provider.init();
const [remoteState] = await provider.getRemoteState(['show-new-nav']);
evaluate(showNewNav, remoteState, { userId: 'user_123' });

Use FlagsClient instead of calling evaluate() directly

Section titled “Use FlagsClient instead of calling evaluate() directly”

Calling evaluate() by hand works for a single flag, but for anything real — remote state fetching, caching, dependsOn resolution across multiple flags, live updates — use FlagsClient, which wraps a provider and every registered FlagDefinition:

import { FlagsClient, LocalProvider } from '@useoptimus/core';
const client = new FlagsClient({
definitions: [showNewNav],
provider: new LocalProvider([
{ key: 'show-new-nav', enabled: true, updatedAt: new Date().toISOString() },
]),
});
await client.init();
client.evaluate('show-new-nav', { userId: 'user_123' });
client.evaluateAll({ userId: 'user_123' });

FlagsClient also owns failureMode semantics (closed/open/ lastKnown), subscribe() for live-update notification, and setOverrides()/clearOverrides() for forcing a flag’s value regardless of everything else. For real network traffic, swap LocalProvider for HttpPollingProvider or SseProvider (both exported from @useoptimus/core) — see State Providers for worked examples of each, including failure handling and caching.

For common flag shapes, kinds.ts ships pre-filled trait bundles instead of writing out a FlagDefinition by hand:

import { defineKillSwitch, defineExperiment } from '@useoptimus/core';
const maintenanceMode = defineKillSwitch({ key: 'maintenance-mode', defaultValue: false });
const checkoutExperiment = defineExperiment({
key: 'checkout-v2',
defaultValue: 'control',
variants: [
{ key: 'control', value: 'control', weight: 50 },
{ key: 'treatment', value: 'treatment', weight: 50 },
],
});
  • State Providers — wire up HttpPollingProvider/SseProvider for real remote flag state.
  • Bucketing & Salting — how percentage rollouts and A/B variants are assigned.
  • Targeting Rules — attribute/percentage/ semver/date conditions and how the rule list is evaluated.
  • Pick your framework’s adapter: React, Angular, or Node / SSR.
  • DevTools — force a flag value during QA/E2E without touching remote state.