moving-from-fullstack-to-feature-experimentation
Wingify FullStack vs FE
A comprehensive guide to understanding the differences and migrating from Wingify FullStack to Feature Experimentation (FE).
Introduction
DEPRECATED Wingify FullStack (FS) — The original server-side SDK for A/B testing and feature management. Only receiving critical bug fixes and security patches.
CURRENT Wingify FE (Feature Experimentation) — The actively developed replacement with a modernized API and improved developer experience.
Same Purpose, New Philosophy
Both SDKs enable server-side A/B testing and feature management. The key difference lies in their design philosophy:
| FullStack | FE |
|---|---|
| Campaign-Centric — Everything revolves around campaigns. You activate users into campaigns and track goals per campaign. | Feature-Flag-Centric — Features are the primary unit. You evaluate flags and track events independently of campaigns. |
Installation
| Aspect | FullStack | FE |
|---|---|---|
| Package Name | vwo-node-sdk | wingify-fme-node-sdk |
| Install Command | npm install vwo-node-sdk | npm install wingify-fme-node-sdk |
| Node.js Version | >= 6.10.0 | >= 12.0 |
Initialization
FullStack: Two-Step Process
const vwoSDK = require('vwo-node-sdk');
// Step 1: Fetch settings file
const settingsFile = await vwoSDK.getSettingsFile(
accountId,
sdkKey
);
// Step 2: Launch SDK
const client = vwoSDK.launch({
settingsFile: settingsFile
});FE: Single-Step Process
const { init } = require('wingify-fme-node-sdk');
// Single initialization call
const wingifyClient = await init({
accountId: '123456',
sdkKey: 'your-sdk-key',
});
// Optional: auto-refresh settings
const wingifyClient = await init({
accountId: '123456',
sdkKey: 'your-sdk-key',
pollInterval: 60000,
});Key Difference: FE handles settings file fetching internally. No need for a separate
getSettingsFile()call.
Running an A/B Test
FullStack Approach (Campaign-Centric)
const vwoSDK = require('vwo-node-sdk');
// Initialize
const settingsFile = await vwoSDK.getSettingsFile(accountId, sdkKey);
const client = vwoSDK.launch({ settingsFile });
// Activate user in campaign - returns variation name
const variation = wingifyClient.activate(
'checkout-button-campaign', // campaignKey
'user-123', // userId
{
customVariables: { plan: 'premium' },
userAgent: req.headers['user-agent']
}
);
// Handle variations by name
if (variation === 'Control') {
buttonColor = 'blue';
} else if (variation === 'Variation-1') {
buttonColor = 'green';
} else {
// User not in campaign (null returned)
buttonColor = 'blue';
}
// Track conversion (requires campaignKey)
wingifyClient.track(
'checkout-button-campaign', // campaignKey
'user-123', // userId
'purchase-completed' // goalIdentifier
);FE Approach (Feature-Flag-Centric)
const { init } = require('wingify-fme-node-sdk');
// Initialize
const wingifyClient = await init({
accountId: '123456',
sdkKey: 'your-sdk-key',
});
// Create user context once, reuse it
const userContext = {
id: 'user-123',
customVariables: { plan: 'premium' },
userAgent: req.headers['user-agent']
};
// Get feature flag - contains all variation data
const flag = await wingifyClient.getFlag('checkout-button', userContext);
// Check if enabled and get variables
if (flag.isEnabled()) {
buttonColor = flag.getVariable('buttonColor', 'blue');
buttonText = flag.getVariable('buttonText', 'Buy Now');
}
// Track event (no campaign key needed)
wingifyClient.trackEvent('purchase-completed', userContext);Key Differences:
- User ID: FS passes it per method; FE uses a reusable
userContextobject- Variation access: FS returns variation name to check; FE provides variables directly via
getVariable()- Tracking: FS requires
campaignKey; FE tracks events independently
Variables Over Variations
One of the most important changes in FE is the shift from variation-name-based logic to variable-driven logic. This isn't just a syntax change—it's a fundamental improvement in how you architect experiments.
The Problem with Variation Names (FS Approach)
In FullStack, you typically check which variation a user is in and branch your code accordingly:
// FullStack: Variation-name-based logic (NOT recommended)
const variation = client.activate('checkout-experiment', userId);
if (variation === 'Control') {
buttonColor = 'blue';
buttonText = 'Buy Now';
showBadge = false;
} else if (variation === 'Variation-1') {
buttonColor = 'green';
buttonText = 'Buy Now';
showBadge = true;
} else if (variation === 'Variation-2') {
buttonColor = 'orange';
buttonText = 'Add to Cart';
showBadge = true;
}Problems with this approach:
Wingify - Tight coupling: Your code is tightly coupled to variation names defined in
- Code changes for experiments: Adding a new variation requires a code deployment
- Hard to maintain: Variation logic is scattered across your codebase
- Rigid: Non-developers cannot iterate on experiments without code changes
The Solution: Variable-Driven Logic (FE Approach)
FE encourages you to define variables (buttonColor, buttonText, showBadge) in the Wingify dashboard, with each variation specifying different values. Your code simply reads these variables:
// FE: Variable-driven logic (RECOMMENDED)
const flag = await wingifyClient.getFlag('checkout-experiment', userContext);
if (flag.isEnabled()) {
// Variables are configured in Wingify dashboard, not hardcoded
const buttonColor = flag.getVariable('buttonColor', 'blue');
const buttonText = flag.getVariable('buttonText', 'Buy Now');
const showBadge = flag.getVariable('showBadge', false);
renderButton(buttonColor, buttonText, showBadge);
}Benefits of variable-driven logic:
- Decoupled: Code doesn't know or care about variation names
Wingify - No deployments: Add new variations or modify existing ones entirely in- Single source of truth: All experiment configuration lives in the dashboard
- Team empowerment: Product managers can iterate without engineering
Side-by-Side Comparison
FullStack (Variation-Based):
// Code knows about variations
if (variation === 'Variation-1') {
// Hardcoded behavior
showNewCheckout();
} else {
showOldCheckout();
}
// Adding Variation-2 requires:
// 1. Code change
// 2. Code review
// 3. DeploymentFE (Variable-Based):
// Code only knows about variables
const useNewFlow = flag.getVariable(
'useNewCheckout', false
);
if (useNewFlow) {
showNewCheckout();
} else {
showOldCheckout();
}
// Adding variations:
// Just update Wingify dashboard!When You Need Variation Names
Sometimes you genuinely need to know which variation a user is in (for logging, analytics, or debugging). In FE, the recommended approach is to create an explicit variation_name variable:
// Create a 'variationName' variable in Wingify dashboard
// Control: variationName = "control"
// Variation-1: variationName = "green-button"
// Variation-2: variationName = "orange-button"
const flag = await wingifyClient.getFlag('checkout-experiment', userContext);
const variationName = flag.getVariable('variationName', 'control');
// Use for logging/analytics only, not branching logic
analytics.track('experiment_viewed', { variation: variationName });Best Practice: Treat variation names as configuration artifacts, not API contracts. Your code should depend on variables (what to do), not variations (which bucket the user is in). This aligns with OpenFeature best practices.
API Method Mapping
| Purpose | FullStack | FE |
|---|---|---|
| Initialize SDK | getSettingsFile() + launch() | init() |
| Get variation | activate(campaignKey, userId) | getFlag(featureKey, userContext) |
| Check if enabled | isFeatureEnabled(campaignKey, userId) | flag.isEnabled() |
| Get variable value | getFeatureVariableValue(campaignKey, variableKey, userId) | flag.getVariable(key, defaultValue) |
| Get all variables | N/A (call per variable) | flag.getVariables() |
| Track conversion | track(campaignKey, userId, goalId) | trackEvent(eventName, userContext) |
| Push attributes | push(tagKey, tagValue, userId) | setAttribute(name, value, userContext) |
Conceptual Differences
| Aspect | FullStack | FE |
|---|---|---|
| Primary Unit | Campaign | Feature Flag |
| User Identification | userId string passed to each method | userContext object created once, reused |
| Async Pattern | Callback-based with opt-in promises (returnPromiseFor: { activate: true }) | Native async/await by default |
| Event Tracking | Tied to campaigns (requires campaignKey) | Event-based, campaign-agnostic |
| Custom Variables | Passed in options per method call | Part of userContext.customVariables |
| Settings Management | Manual: fetch, store, refresh via webhooks/polling | Automatic with pollInterval option |
Quick Migration Checklist
- Update package:
vwo-node-sdk→wingify-fme-node-sdk - Change import:
require('vwo-node-sdk')→const { init } = require('wingify-fme-node-sdk') - Replace
getSettingsFile()+launch()with singleinit() - Create
userContextobjects instead of passinguserIdstrings - Replace
activate()withgetFlag() - Replace
isFeatureEnabled()withflag.isEnabled() - Replace
getFeatureVariableValue()withflag.getVariable() - Replace
track()withtrackEvent()(remove campaignKey) - Move
customVariablesfrom options touserContext - Update Node.js to >= 12.0 if needed
Detailed Migration Guide
Step 1: Update Package
# Remove old package
npm uninstall vwo-node-sdk
# Install new package
npm install wingify-fme-node-sdk --saveStep 2: Update Import Statement
Before (FS):
const vwoSDK = require('vwo-node-sdk');After (FE):
const { init } = require('wingify-fme-node-sdk');Step 3: Refactor Initialization
Before (FS):
const settingsFile = await vwoSDK.getSettingsFile(accountId, sdkKey);
const client = vwoSDK.launch({
settingsFile,
isDevelopmentMode: false,
logging: { level: 'ERROR' }
});After (FE):
const wingifyClient = await init({
accountId: '123456',
sdkKey: 'your-sdk-key',
logger: { level: 'ERROR' },
pollInterval: 60000 // auto-refresh settings
});Step 4: Create User Context
Before (FS) — User ID and options passed to each method:
const userId = 'user-123';
const options = {
customVariables: { plan: 'premium' },
userAgent: req.headers['user-agent'],
userIpAddress: req.ip
};
client.activate(campaignKey, userId, options);
client.track(campaignKey, userId, goalId, options);After (FE) — User context created once and reused:
const userContext = {
id: 'user-123',
customVariables: { plan: 'premium' },
userAgent: req.headers['user-agent'],
ipAddress: req.ip
};
const flag = await wingifyClient.getFlag(featureKey, userContext);
wingifyClient.trackEvent(eventName, userContext);Step 5: Migrate A/B Test Logic
Before (FS):
const variation = client.activate('button-test', userId, options);
if (variation === 'Control') {
showBlueButton();
} else if (variation === 'Variation-1') {
showGreenButton();
}After (FE):
const flag = await wingifyClient.getFlag('button-test', userContext);
if (flag.isEnabled()) {
const color = flag.getVariable('buttonColor', 'blue');
showButton(color);
} else {
showBlueButton();
}Step 6: Migrate Event Tracking
Before (FS):
// Track requires campaign key
client.track('button-test', userId, 'button-clicked');
// Track across multiple campaigns
client.track(['campaign-1', 'campaign-2'], userId, 'purchase');
// Revenue tracking
client.track('campaign', userId, 'revenue-goal', {
revenueValue: 99.99
});After (FE):
// Track event (no campaign key needed)
wingifyClient.trackEvent('button-clicked', userContext);
// Same event works across all campaigns automatically
wingifyClient.trackEvent('purchase', userContext);
// Event with properties
wingifyClient.trackEvent('revenue-goal', userContext, {
revenue: 99.99
});Step 7: Migrate Variable Access
Before (FS):
// Check feature and get variables separately
const isEnabled = client.isFeatureEnabled('new-checkout', userId);
if (isEnabled) {
const headerText = client.getFeatureVariableValue(
'new-checkout', 'headerText', userId
);
const showBanner = client.getFeatureVariableValue(
'new-checkout', 'showBanner', userId
);
}After (FME):
// Get flag once, access all variables
const flag = await wingifyClient.getFlag('new-checkout', userContext);
if (flag.isEnabled()) {
const headerText = flag.getVariable('headerText', 'Welcome');
const showBanner = flag.getVariable('showBanner', false);
// Or get all variables at once
const allVars = flag.getVariables();
}Summary
| Aspect | FullStack (Deprecated) | FE (Current) |
|---|---|---|
| Package | vwo-node-sdk | wingify-fme-node-sdk |
| Node.js | >= 6.10.0 | >= 12.0 |
| Init Pattern | Two-step (fetch + launch) | Single init() call |
| User ID | String per method | userContext object |
| Get Variation | activate() → string | getFlag() → flag object |
| Variables | One call per variable | flag.getVariable() or getVariables() |
| Tracking | Requires campaignKey | Event-based, campaign-agnostic |
| Async | Opt-in promises | Native async/await |
| Status | Critical fixes only | Actively developed |
Need Help? For assistance migrating from FullStack to FE, contact Wingify support at [email protected]
Updated about 1 month ago