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
| Unleash | A vs B |
|---|---|
| Toggle / feature flag | Feature 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.name | Flag<T>.variationKey |
Variant.enabled | Flag<T>.isEnabled() |
Variant.payload.value | Flag<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 strategy | Custom audience condition (attribute-based rule) |
| Unleash Proxy | A vs B CDN datafile delivery (no proxy to self-host) |
| No built-in event tracking | client.track(eventKey, { value?, properties? }) |
| No holdout concept | FlagDatafileHoldout (available on all plans) |
| No multi-context | MultiContext (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):
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});A vs B, Node:
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 itUnleash, Browser (@unleash/proxy-client-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>A vs B, React:
import { AvsbProvider } from '@avsbhq/react';<AvsbProvider sdkKey="sdk_production_xxxxxxxxxxxxxxxx" context={{ kind: 'user', key: 'u_123' }}> <YourApp /></AvsbProvider>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:
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);A vs B:
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' });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:
// 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';A vs B:
// 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[]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):
// 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,});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):
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);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:
unleash.on('update', () => { // Toggle definitions refreshed});A vs B:
client.on('configUpdate', ({ publishedAt, reason }) => { // reason: 'poll' | 'stream' | 'manual'});client.on('flagChange', ({ flagKey, previousValue, newValue }) => { // A specific flag value changed});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:
import { fetchDatafile } from '@avsbhq/browser/server';const datafile = await fetchDatafile(process.env.AVSB_SDK_KEY!);<AvsbProvider sdkKey="..." context={ctx} bootstrap={datafile ?? undefined}> <YourApp /></AvsbProvider>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:
unleash.destroy();A vs B:
await server.close(); // flushes events, stops pollingTesting
Unleash, test setup:
import { createTestUnleashInstance } from 'unleash-client/test';const { client, repo } = createTestUnleashInstance();repo.setToggle('show_banner', true);A vs B, mock client:
import { createMockClient } from '@avsbhq/test';const mock = createMockClient({ flags: { show_banner: true, homepage_theme: 'blue', },});Cutover checklist
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.
Remove Unleash packages
Uninstall unleash-client, @unleash/proxy-client-react, and any related packages.
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.
Install A vs B packages
Install @avsbhq/node, @avsbhq/browser, and @avsbhq/react as needed.
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.
Replace isEnabled calls
Replace unleash.isEnabled(key, ctx) with server.forUser(ctx).getBoolFlag(key, false).isEnabled().
Replace getVariant calls
Replace unleash.getVariant(key, ctx) with the appropriate typed evaluator. Map variant.name → flag.variationKey, variant.payload.value → flag.value.
Add event tracking
Add client.track(eventKey, { value, properties }) at conversion points you want to measure in A/B tests.
Update tests
Replace Unleash test utilities with createMockClient from @avsbhq/test.
Verify and deploy
Run npm run build and npx tsc --noEmit. Confirm flag evaluations in a staging environment before promoting to production traffic.