← All posts
By Signal Team

Signal: A TypeScript-First Feature Flag Platform Built for Production

What most feature flag platforms get wrong, and the architectural decisions we made to fix them.

engineeringarchitecturelaunch

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.

Join the waitlist

Ship your next feature
with confidence.

Join the waitlist. We will reach out personally before we open access.

No spam. One email when we open.