The problem with existing feature flag tools
Feature flags are conceptually simple. In practice, most platforms make them painful to operate at scale.
The most common failure modes:
- Evaluation latency on the hot path. Server-side evaluation means a network call per flag check. Even cached, this adds overhead to every request.
- No type safety. Flag keys are strewn across the codebase as raw strings. A typo silently falls back to the default value. Refactors become dangerous.
- Coarse-grained flag types. Many platforms treat flags as boolean only. Variants, thresholds, and JSON configs end up as separate systems bolted on later.
- Real-time as an afterthought. Polling at 30-second intervals means flag changes take up to 30 seconds to propagate. In an incident, that matters.
Signal was designed to address all four.
Local evaluation with zero latency
Signal's server SDK downloads the full flag ruleset at startup and evaluates locally. Flag checks are synchronous — no network call on the hot path.
const signal = getServerInstance();
// Synchronous — no await, no network, sub-millisecond
const enabled = signal.isEnabledSync('checkout-v2', { userId: req.user.id });The ruleset is kept in sync via WebSocket. When a flag changes in the dashboard, every connected SDK instance receives a FLAG_UPDATED event and updates its local cache in under 50ms.
This means you get the control of server-side management with the performance of in-process evaluation.
Type-safe flag keys
Flag keys in Signal are typed constants, not raw strings. Define them once:
import { createFeatureFlag } from '@code-signal/signal';
export const FLAGS = {
CHECKOUT_V2: createFeatureFlag('checkout-v2'),
DARK_MODE: createFeatureFlag('dark-mode'),
MAX_ITEMS: createFeatureFlag('max-items'),
} as const;Your IDE will autocomplete them. TypeScript will catch typos at compile time. Renaming a flag is a safe refactor.
Four flag value types
Signal supports four flag value types, all with the same targeting and rollout engine:
| Type | Example use case |
|---|---|
boolean |
Feature gates, killswitches, maintenance windows |
string |
Copy variants, theme names, experiment arms |
number |
Rate limits, thresholds, numeric config |
json |
Full configuration objects, layout configs |
// Boolean
const enabled = await signal.isEnabled(FLAGS.CHECKOUT_V2, ctx);
// String variant
const theme = await signal.getStringValue(FLAGS.UI_THEME, 'default', ctx);
// Number threshold
const limit = await signal.getNumberValue(FLAGS.MAX_ITEMS, 10, ctx);
// JSON config
const config = await signal.getJsonValue(FLAGS.GRID_CONFIG, defaultConfig, ctx);Deterministic bucketing
Progressive rollouts use deterministic hash-based bucketing:
bucket = hash(userId + flagKey) % 100
enabled = bucket < rolloutPercentage
Given the same user ID and flag key, the result is always the same — regardless of which SDK instance handles the request, which server processes it, or how many times the user refreshes. No sessions, no cookies, no sticky routing required.
A user at 10% stays at 10% as you scale the rollout to 50%, then 100%.
OpenFeature support
Signal ships OpenFeature providers out of the box:
import { OpenFeature } from '@openfeature/server-sdk';
import { SignalServerProvider } from '@code-signal/signal';
await OpenFeature.setProviderAndWait(new SignalServerProvider(config));
const client = OpenFeature.getClient();
const enabled = await client.getBooleanValue('checkout-v2', false, { targetingKey: userId });If you are already on OpenFeature, Signal is a drop-in backend with no application code changes.
React bindings
For client-side applications, Signal ships React hooks that subscribe to real-time flag updates:
import { useFeatureFlag } from '@code-signal/signal';
function CheckoutButton() {
const { enabled, loading } = useFeatureFlag('checkout-v2');
if (loading) return <Skeleton />;
return enabled ? <NewCheckout /> : <LegacyCheckout />;
}The hook re-renders automatically when the flag changes — no polling, no page reload.
Private beta
Signal is in private beta. We are working closely with early users to validate the platform before general availability.
Join the waitlist to get early access, locked-in pricing, and direct access to the engineering team.