Migrate from Unleash to A vs B
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) |
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.
1import { initialize } from 'unleash-client';2
3const unleash = initialize({4 url: 'https://unleash.example.com/api',5 appName: 'my-app',6 customHeaders: { Authorization: 'YOUR_API_TOKEN' },7});8
9unleash.on('synchronized', () => {10 // Client is ready11});1import { AvsbServer } from '@avsbhq/node';2
3const server = new AvsbServer({ sdkKey: process.env.AVSB_SDK_KEY! });4const result = await server.onReady();5// result.success → fully initialized from network6// result.degraded → running on defaults; polling continues1import { FlagProvider } from '@unleash/proxy-client-react';2
3const config = {4 url: 'https://unleash-proxy.example.com/proxy',5 clientKey: 'proxy-client-secret',6 appName: 'my-app',7};8
9<FlagProvider config={config}>10 <App />11</FlagProvider>1import { AvsbProvider } from '@avsbhq/react';2
3<AvsbProvider4 sdkKey="sdk_production_abc123"5 context={{ kind: 'user', key: 'u_123' }}6>7 <App />8</AvsbProvider>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.
1const ctx = {2 userId: 'u_123',3 sessionId: 'sess_abc',4 remoteAddress: '192.168.1.1',5 properties: {6 plan: 'pro',7 country: 'US',8 },9};10const enabled = unleash.isEnabled('show_banner', ctx);1// Flatten userId, sessionId, and properties into a single EvalContext2const ctx = {3 kind: 'user',4 key: 'u_123', // was userId5 sessionId: 'sess_abc',6 remoteAddress: '192.168.1.1',7 plan: 'pro', // was properties.plan8 country: 'US', // was properties.country9};10
11// Server — per call12server.forUser(ctx).getBoolFlag('show_banner', false);13
14// Browser — bound at construction, updated via mutations15client.identify(ctx);16client.updateAttributes({ plan: 'enterprise' });17
18// Anonymous → identified stitching at sign-up19await client.alias(20 { kind: 'user', key: 'anon_xyz' },21 { kind: 'user', key: 'u_123' }22);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.
1// Boolean toggle2const showBanner = unleash.isEnabled('show_banner', ctx);3
4// Variant toggle5const variant = unleash.getVariant('homepage_theme', ctx);6// variant.name → 'blue' | 'green' | 'disabled'7// variant.enabled → true/false8// variant.payload → { type: 'string', value: 'blue' } | undefined9const theme = variant.enabled ? variant.payload?.value ?? 'default' : 'default';1// Boolean flag2const showBanner = server.forUser(ctx).getBoolFlag('show_banner', false).isEnabled();3
4// String variant — variationKey is the variant name5const themeFlag = server.forUser(ctx).getStringFlag('homepage_theme', 'default');6const theme = themeFlag.value; // 'blue' | 'green' | 'default'7const variantName = themeFlag.variationKey; // 'blue' | 'green' | null8
9// Full envelope10const flag = server.forUser(ctx).getStringFlag('homepage_theme', 'default');11flag.isEnabled() // true if a rule matched and value is truthy12flag.source // 'rule' | 'default' | 'holdout' | 'sticky' | ...13flag.ruleId // matched rule id or null14flag.reasons // string[]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.
1// Browser2client.track('purchase_completed', {3 value: 49.99,4 properties: { orderId: 'ord_99', plan: 'pro' },5});6
7// Server8server.track('purchase_completed', {9 context: { kind: 'user', key: 'u_123' },10 value: 49.99,11 properties: { orderId: 'ord_99' },12});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.
1import type { MultiContext } from '@avsbhq/core';2
3const ctx: MultiContext = {4 kind: 'multi',5 user: { kind: 'user', key: 'u_123', plan: 'pro' },6 organization: { kind: 'organization', key: 'org_42', tier: 'enterprise' },7};8server.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.
1unleash.on('update', () => {2 // Toggle definitions refreshed3});1client.on('configUpdate', ({ publishedAt, reason }) => {2 // reason: 'poll' | 'stream' | 'manual'3});4
5client.on('flagChange', ({ flagKey, previousValue, newValue }) => {6 // A specific flag value changed7});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.
1import { fetchDatafile } from '@avsbhq/node/server';2const datafile = await fetchDatafile(process.env.AVSB_SDK_KEY!);3
4<AvsbProvider sdkKey="..." context={ctx} bootstrap={datafile ?? undefined}>5 {children}6</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
1unleash.destroy();1await server.close(); // flushes events, stops pollingTesting
1import { createTestUnleashInstance } from 'unleash-client/test';2
3// NOTE: verify before publish — Unleash test utility API subject to change in upstream SDK4const { client, repo } = createTestUnleashInstance();5repo.setToggle('show_banner', true);1import { createMockClient } from '@avsbhq/test';2
3const mock = createMockClient({4 flags: {5 show_banner: true,6 homepage_theme: 'blue',7 },8});Cutover checklist
Recreate toggles in A vs B dashboard
Remove Unleash packages
unleash-client, @unleash/proxy-client-react, and any related packages.Shut down Unleash Proxy (if self-hosted)
Install A vs B packages
@avsbhq/node, @avsbhq/browser, and @avsbhq/react as needed.Migrate context construction
userId, sessionId, remoteAddress, properties.*) into a flat EvalContext: rename userId → key, add kind: 'user', and move properties.* to top-level attributes.Replace isEnabled calls
unleash.isEnabled(key, ctx) with server.forUser(ctx).getBoolFlag(key, false).isEnabled().Replace getVariant calls
unleash.getVariant(key, ctx) with the appropriate typed evaluator. Map variant.name → flag.variationKey, variant.payload.value → flag.value.Add event tracking
client.track(eventKey, { value, properties }) at conversion points you want to measure in A/B tests.Update tests
createMockClient from @avsbhq/test.Verify and deploy
npm run build and npx tsc --noEmit. Confirm flag evaluations in a staging environment before promoting to production traffic.