From Unleash

Unleash is an open-source feature management platform with a simple toggle model and gradual rollout strategies. A vs B covers the same feature toggle surface (boolean flags, gradual rollouts, user targeting) and adds typed string/number/JSON values, a built-in A/B testing engine, holdouts, and a formal event-tracking API. If you self-host Unleash, the migration also moves you to A vs B's managed platform. Your toggle names, strategy configurations, and gradual rollout percentages do not transfer automatically. You will recreate them in the A vs B dashboard, but your evaluation call sites are a close one-to-one swap.

Concept mapping

UnleashA vs B
Toggle / feature flagFeature flag (recreate in A vs B dashboard; a hyphen in its name becomes an underscore in the key)
unleash.isEnabled('flag-key', ctx)client.getBoolFlag('flag_key', false).isEnabled()
unleash.getVariant('flag-key', ctx)client.getFlag('flag_key', default) (.variationKey is the variant name)
Variant.nameFlag<T>.variationKey
Variant.enabledFlag<T>.isEnabled()
Variant.payload.valueFlag<T>.value
IToggle (toggle entity)Flag<T> evaluation result
Unleash context (userId, sessionId, remoteAddress, properties)EvalContext (context.key is the primary bucketing ID)
Activation strategy (gradualRollout, userWithId, etc.)Audience rule + traffic allocation on the flag rule in the dashboard
Custom strategyCustom audience condition (attribute-based rule)
Unleash ProxyA vs B CDN datafile delivery (no proxy to self-host)
No built-in event trackingclient.track(eventKey, { value?, properties? })
No holdout conceptFlagDatafileHoldout (available on all plans)
No multi-contextMultiContext (new capability gained on migration)
unleash.destroy()server.close()

Client construction

Unleash's Node SDK connects to the Unleash server (or proxy) via a URL and app name. A vs B connects to its CDN using a per-environment SDK key.

Unleash, Node (unleash-client):

TypeScript
import { initialize } from 'unleash-client';const unleash = initialize({  url: 'https://unleash.example.com/api',  appName: 'my-app',  customHeaders: { Authorization: 'YOUR_API_TOKEN' },});unleash.on('synchronized', () => {  // Client is ready});
TypeScript11 lines

A vs B, Node:

TypeScript
import { AvsbServer } from '@avsbhq/node';const server = new AvsbServer({ sdkKey: process.env.AVSB_SDK_KEY! });const result = await server.onReady();// result.success  →  a datafile loaded, so flags are answerable// result.degraded →  browser client only; the server SDK never sets it
TypeScript6 lines

Unleash, Browser (@unleash/proxy-client-react):

TypeScript React
import { FlagProvider } from '@unleash/proxy-client-react';const config = {  url: 'https://unleash-proxy.example.com/proxy',  clientKey: 'proxy-client-secret',  appName: 'my-app',};<FlagProvider config={config}>  <YourApp /></FlagProvider>
TypeScript React11 lines

A vs B, React:

TypeScript React
import { AvsbProvider } from '@avsbhq/react';<AvsbProvider  sdkKey="sdk_production_xxxxxxxxxxxxxxxx"  context={{ kind: 'user', key: 'u_123' }}>  <YourApp /></AvsbProvider>
TypeScript React8 lines
Info

Unleash requires a self-hosted Unleash Proxy for browser SDK clients (to avoid leaking all flag definitions). A vs B ships per-environment datafiles from its own CDN, so there is no proxy to host or maintain.

Identity model

Unleash accepts a context object on each evaluation call. The context has fixed fields (userId, sessionId, remoteAddress) plus a properties map for custom attributes. A vs B uses a flat EvalContext where all targeting attributes live at the top level under a typed kind.

Unleash:

TypeScript
const ctx = {  userId: 'u_123',  sessionId: 'sess_abc',  remoteAddress: '192.168.1.1',  properties: {    plan: 'pro',    country: 'US',  },};const enabled = unleash.isEnabled('show_banner', ctx);
TypeScript10 lines

A vs B:

TypeScript
import { AvsbClient } from '@avsbhq/browser';// Flatten userId, sessionId, and properties into a single EvalContextconst ctx = {  kind: 'user',  key: 'u_123',       // was userId  sessionId: 'sess_abc',  remoteAddress: '192.168.1.1',  plan: 'pro',        // was properties.plan  country: 'US',      // was properties.country};// Server: per callserver.forUser(ctx).getBoolFlag('show_banner', false);// Browser: bound at construction, updated via mutationsconst client = new AvsbClient({ sdkKey: 'sdk_production_xxxxxxxxxxxxxxxx' });client.identify(ctx);client.updateAttributes({ plan: 'enterprise' });// Anonymous → identified stitching at sign-up. Synchronous: it records the// moment, it does not move past decisions.client.alias(  { kind: 'user', key: 'anon_xyz' },  { kind: 'user', key: 'u_123' });
TypeScript26 lines

Flag evaluation

Unleash has two evaluation methods: isEnabled (boolean) and getVariant (variant with optional payload). A vs B maps both into the Flag<T> envelope.

Unleash:

TypeScript
// Boolean toggleconst showBanner = unleash.isEnabled('show_banner', ctx);// Variant toggleconst variant = unleash.getVariant('homepage_theme', ctx);// variant.name       → 'blue' | 'green' | 'disabled'// variant.enabled    → true/false// variant.payload    → { type: 'string', value: 'blue' } | undefinedconst theme = variant.enabled ? variant.payload?.value ?? 'default' : 'default';
TypeScript9 lines

A vs B:

TypeScript
// Boolean flagconst showBanner = server.forUser(ctx).getBoolFlag('show_banner', false).isEnabled();// String variant: variationKey is the variant nameconst themeFlag = server.forUser(ctx).getStringFlag('homepage_theme', 'default');const theme    = themeFlag.value;        // 'blue' | 'green' | 'default'const variantName = themeFlag.variationKey; // 'blue' | 'green' | null// Full envelopeconst flag = server.forUser(ctx).getStringFlag('homepage_theme', 'default');flag.isEnabled()   // true for a real decision (rule, holdout, bandit, or                    // override, not just the default) with a truthy valueflag.source        // 'rule' | 'default' | 'holdout' | 'sticky' | ...flag.ruleId        // matched rule id or nullflag.reasons       // string[]
TypeScript15 lines
Tip

Unleash's variant.payload.type field declares whether the payload is a string, number, or JSON. In A vs B, use the appropriate typed evaluator (getStringFlag, getNumberFlag, getJsonFlag<T>) and the type inference is enforced at compile time.

Tracking events

Unleash does not have a built-in event tracking API for metric collection. A vs B adds full event tracking, which is required when you attach metric targets to A/B test rules.

A vs B, event tracking (new capability):

TypeScript
// Browser. `revenue` is money in major units; `value` is the separate// numeric-metric column (items in a cart, seats on a plan).client.track('purchase_completed', { revenue: 49.99 });// Server: the context to attribute the conversion to is explicitserver.track('purchase_completed', {  context: { kind: 'user', key: 'u_123' },  revenue: 49.99,});
TypeScript9 lines

Multi-context

Unleash targets on a single context. A vs B adds multi-context targeting as a new capability on migration, useful when you want to bucket by organization, device, or any other entity type in addition to the user.

A vs B, multi-context (new capability):

TypeScript
import type { MultiContext } from '@avsbhq/core';const ctx: MultiContext = {  kind: 'multi',  user:         { kind: 'user',         key: 'u_123', plan: 'pro' },  organization: { kind: 'organization', key: 'org_42', tier: 'enterprise' },};server.forUser(ctx).getBoolFlag('enterprise_feature', false);
TypeScript8 lines

Streaming updates

Unleash uses a polling model to refresh toggle definitions. A vs B uses the same configurable polling approach with optional SSE push for the browser.

Unleash:

TypeScript
unleash.on('update', () => {  // Toggle definitions refreshed});
TypeScript3 lines

A vs B:

TypeScript
client.on('configUpdate', ({ publishedAt, reason }) => {  // reason: 'poll' | 'stream' | 'manual'});client.on('flagChange', ({ flagKey, previousValue, newValue }) => {  // A specific flag value changed});
TypeScript7 lines

Bootstrap / SSR

Unleash's proxy-based architecture can serve bootstrap values via the proxy API. A vs B uses a FlagDatafile fetched on the server and passed as bootstrap to the client provider.

A vs B, SSR bootstrap:

TypeScript React
import { fetchDatafile } from '@avsbhq/browser/server';const datafile = await fetchDatafile(process.env.AVSB_SDK_KEY!);<AvsbProvider sdkKey="..." context={ctx} bootstrap={datafile ?? undefined}>  <YourApp /></AvsbProvider>
TypeScript React6 lines

Holdouts

Unleash does not have a holdout concept. A vs B includes holdouts on all plans. After migration, create a holdout in the dashboard and associate flags with it to maintain a clean control group across concurrent experiments; held-out users receive source: 'holdout' in the evaluation result and are automatically served the control variation.

Cleanup

Unleash:

TypeScript
unleash.destroy();
TypeScript1 line

A vs B:

TypeScript
await server.close(); // flushes events, stops polling
TypeScript1 line

Testing

Unleash, test setup:

TypeScript
import { createTestUnleashInstance } from 'unleash-client/test';const { client, repo } = createTestUnleashInstance();repo.setToggle('show_banner', true);
TypeScript4 lines

A vs B, mock client:

TypeScript
import { createMockClient } from '@avsbhq/test';const mock = createMockClient({  flags: {    show_banner: true,    homepage_theme: 'blue',  },});
TypeScript8 lines

Cutover checklist

1

Recreate toggles in A vs B dashboard

Unleash toggle definitions do not auto-import. Recreate each toggle as a flag in the A vs B dashboard, configuring targeting rules and rollout percentages to match your existing Unleash strategies.

2

Remove Unleash packages

Uninstall unleash-client, @unleash/proxy-client-react, and any related packages.

3

Shut down Unleash Proxy (if self-hosted)

Once the migration is complete and traffic has shifted, decommission your Unleash Proxy instance. A vs B serves datafiles from its own CDN.

4

Install A vs B packages

Install @avsbhq/node, @avsbhq/browser, and @avsbhq/react as needed.

5

Migrate context construction

Flatten Unleash's context fields (userId, sessionId, remoteAddress, properties.*) into a flat EvalContext: rename userId → key, add kind: 'user', and move properties.* to top-level attributes.

6

Replace isEnabled calls

Replace unleash.isEnabled(key, ctx) with server.forUser(ctx).getBoolFlag(key, false).isEnabled().

7

Replace getVariant calls

Replace unleash.getVariant(key, ctx) with the appropriate typed evaluator. Map variant.name → flag.variationKey, variant.payload.value → flag.value.

8

Add event tracking

Add client.track(eventKey, { value, properties }) at conversion points you want to measure in A/B tests.

9

Update tests

Replace Unleash test utilities with createMockClient from @avsbhq/test.

10

Verify and deploy

Run npm run build and npx tsc --noEmit. Confirm flag evaluations in a staging environment before promoting to production traffic.

Was this helpful?