@amirafa/vuexp
v1.0.7
Published
Lightweight Vue 3 plugin for A/B testing and experiments with variant tracking.
Maintainers
Readme
Vuexp is a lightweight and flexible Vue 3 plugin for A/B testing.
It helps you define experiments, control variant traffic, track user behavior, and manage exposure — all directly within your Vue components.
📚 Table of Contents
- 📦 Installation
- 🧠 Fundamentals
- ⚙️ Setup
- 🧩 Usage
- 🧾 Experiment Props
- 🧩 Component Expose
- 🎧 Component Emits
- 📡 Tracking System
- 🎯 Events
- 🌐 Remote Config Example
- 🔄 Versioning & Cache Behavior
- 🧮 Holdout Group
- 👤 Setting User Context
- 🐞 Debug Mode
- 💡 Best Practices
- 📜 License
- 📅 Changelog
📦 Installation
npm install @amirafa/vuexp
# or
yarn add @amirafa/vuexp🧠 Fundamentals
Each A/B Test can include multiple Experiments.
Every Experiment decides which Variant (A, B, etc.) of your UI to show based on a statistical parameter called Traffic.
For example:
// Example traffic definition
traffic: {
A: 0.6,
B: 0.4,
}- The plugin will randomly assign a user to one of these variants based on the given probabilities.
- Variant A appears ~60% of the time, and B ~40%.
- If total traffic is less than
1.0(e.g.{ A: 0.6, B: 0.2 }), the remaining ratio (0.2) becomes the Holdout group, which sees your defineddefaultVariant.
✅ Vuexp automatically normalizes the traffic and chooses holdouts for you.
Changing the
versionin the global config flushes all saved experiments, so users will get newly assigned variants the next time they appear.
⚙️ Setup
// main.ts or App.vue
import { createApp } from "vue";
import App from "./App.vue";
import { Vuexp } from "@amirafa/vuexp";
const app = createApp(App);
app.use(Vuexp, {
version: "1.0.0", // Change to reset cached experiments
debug: true, // Show logs in console (optional)
// Optional remote config & tracking
experimentsFetchApi: "https://example.com/config.php",
experimentsTrackApi: "https://example.com/track.php",
userId: undefined, // optional -better to set it after login (setUserContext({userId:<user_id>}))
visitorId: "abc123", // optional - better to generate it based on browser
// Global onTrack callback (fires after each track)
onTrack(payload, response) {
console.log("[onTrack()] Payload Sent", payload);
console.log("[onTrack()] Response Received", response);
},
// Track mode options
trackMode: {
mode: "interval", // 'immediate' | 'interval'
interval: 10000, // flush interval in ms (optional)
batchSize: 2, // events per flush (optional)
},
// Default experiment fallback
default: {
variants: ["A", "B"],
defaultVariant: "A",
traffic: { A: 0.9, B: 0.6 },
},
});
app.mount("#app");🔺 Config Priority
Vuexp decides experiment settings in this order:
Remote Fetch → Component Props → Global Config → Default🧩 Usage
Vuexp offers multiple ways to define experiments.
a) Component with Props
<Experiment
experiment="experiment-button"
:is="{ A: ButtonTypeA, B: ButtonTypeB }"
:variants="['A', 'B']"
:traffic="{ A: 0.6, B: 0.4 }"
/>b) Component with Slots
<Experiment
experiment="experiment-button"
:variants="['A', 'B']"
:traffic="{ A: 0.6, B: 0.4 }"
>
<template #A>
<ButtonTypeA />
</template>
<template #B>
<ButtonTypeB />
</template>
</Experiment>c) Component with Slot Props
<Experiment
experiment="experiment-button"
:variants="['A', 'B']"
:traffic="{ A: 0.6, B: 0.4 }"
v-slot="{ variant, track }"
>
<div
@click="
track('click', {
attributes: {
info: `[experiment-button] clicked at ${new Date().toISOString()}`,
},
})
"
>
<ButtonTypeA v-if="variant === 'A'" />
<ButtonTypeB v-else-if="variant === 'B'" />
<span v-else>Holdout</span>
</div>
</Experiment>d) Composable: useExperiment
<template>
<div>
<ButtonTypeA v-if="variant === 'A'" />
<ButtonTypeB v-if="variant === 'B'" />
</div>
</template>
<script setup lang="ts">
import { useExperiment } from "@amirafa/vuexp";
import ButtonTypeA from "./ButtonTypeA.vue";
import ButtonTypeB from "./ButtonTypeB.vue";
const { variant, track } = useExperiment("btn-use", {
variants: ["A", "B"],
traffic: { A: 0.6, B: 0.4 },
});
</script>🧾 Experiment Props
| Prop | Type | Required | Description |
| ---------------- | --------------------------- | -------- | --------------------------------- |
| experiment | string | ✅ | Unique experiment identifier |
| variants | string[] | ❌ | Available variant names |
| traffic | Record<string, number> | ❌ | Variant probabilities (0–1) |
| defaultVariant | string | ❌ | Variant used for holdouts |
| is | Record<string, Component> | ❌ | Map of variant keys to components |
🧩 Component Expose
Each <Experiment> component exposes a track() method:
track(
event: "exposure" | "click" | "mouseenter" | "mouseleave" | string,
options?: {
attributes?: Record<string, any>;
immediate?: boolean;
}
)Example:
<Experiment
ref="experimentButtonRef"
experiment="experiment-button"
:is="{ A: ButtonTypeA, B: ButtonTypeB }"
:variants="['A', 'B']"
:traffic="{ A: 0.6, B: 0.4 }"
@click="
experimentButtonRef.track('click', {
attributes: {
info: `[btn-slot] clicked at ${new Date().toISOString()}`,
},
})
"
/>🎧 Component Emits
| Event | Payload |
| ------------------------------------------- | ------------------ |
| "click" / "mouseenter" / "mouseleave" | { track, event } |
Example:
<Experiment
experiment="experiment-button"
:is="{ A: ButtonTypeA, B: ButtonTypeB }"
:variants="['A', 'B']"
:traffic="{ A: 0.6, B: 0.4 }"
@click="
({ track, event }) => {
console.log('Event:', event);
track('click', {
attributes: {
info: `[experiment-button] clicked at ${new Date().toISOString()}`,
},
}).then((resp) => {
console.log('[payload]', resp.payload);
console.log('[response]', resp.response);
});
}
"
/>📡 Tracking System
You can track experiment data automatically or manually.
Global Tracking (onTrack)
onTrack(payload, response) {
console.log("[onTrack()] Payload Sent", payload);
console.log("[onTrack()] Response Received", response);
}If experimentsTrackApi is defined, Vuexp sends the data to that endpoint and passes the response here.
Otherwise, only the payload is sent to onTrack (response = undefined).
🧩 Tracking Priority
1️⃣ Global onTrack() → 2️⃣ Local track()Local track() calls always run after the global callback.
⏱️ Track Modes
| Mode | Description |
| ----------- | -------------------------------------------------------------------- |
| immediate | Sends the event right after track() is called |
| interval | Buffers events and sends them after a defined interval or batch size |
Even in interval mode, you can set
{ immediate: true }per event to override behavior.
📤 Track Data Structure
When you define a experimentsTrackApi endpoint in your plugin configuration,
Vuexp automatically sends a payload containing detailed tracking information for every experiment event.
Each record in the payload follows the type:
export type ExperimentTrackRequestType = {
experiment: string; // Experiment name
variant: string; // Assigned variant (A, B, etc.)
version: string; // Event's version
event: EventType; // 'exposure', 'click', 'mouseenter', etc.
timestamp: number; // Unix timestamp of the event
user_id?: string | number; // Optional user ID
visitor_id?: string | number; // Optional visitor ID
attributes?: Record<string, any>; // Custom attributes (optional)
};experiment: The unique identifier of the experiment.variant: The variant that was displayed to the user.version: The version of the test in which the event was tracked.event: The event type that triggered tracking (e.g.,click,exposure).timestamp: Captured automatically at the moment of tracking.user_id/visitor_id: Identifiers that you can set dynamically usingsetUserContext().attributes: Any custom metadata or context (e.g., button label, page, session data, etc.).
Example payload sent to your API:
[
{
"experiment": "experiment-button",
"variant": "A",
"version": "1.0.1"
"event": "click",
"timestamp": 1739923200000,
"user_id": "1000",
"visitor_id": "abc123",
"attributes": {
"info": "Clicked CTA in header"
}
}
]💡 You can safely call
setUserContext()at any time before or after plugin initialization — Vuexp automatically attaches the most recentuser_idorvisitor_idvalues to all future tracking events.
🎯 Events
Vuexp provides 4 main built-in event types:
"exposure" | "click" | "mouseenter" | "mouseleave"- exposure → fired automatically when an experiment first becomes visible
- click, mouseenter, mouseleave → can be triggered manually via
track()or emits - You can also use custom event names for advanced tracking.
Example:
<Experiment
experiment="experiment-button"
:is="{ A: ButtonTypeA, B: ButtonTypeB }"
:variants="['A', 'B']"
:traffic="{ A: 0.6, B: 0.4 }"
@click="
({ track }) =>
track('click', {
attributes: {
info: `[experiment-button] clicked at ${new Date().toISOString()}`,
},
})
"
/>🌐 Remote Config Example
You can host your experiment configuration on a server:
GET https://example.com/config.php
{
"experiments": {
"experiment-button": {
"variants": ["A", "B"],
"traffic": { "A": 0.6, "B": 0.4 },
"defaultVariant": "A"
},
"hero-banner": {
"variants": ["Large", "Compact"],
"traffic": { "Large": 0.5, "Compact": 0.5 }
}
},
"default": {
"variants": ["A", "B"],
"traffic": { "A": 0.5, "B": 0.5 }
}
}Vuexp will automatically merge these configs and use them with highest priority.
🔄 Versioning & Cache Behavior
- Each experiment’s variant is persisted per version.
- Updating
versionin your Vuexp config flushes previous variant assignments. - This ensures that new configurations or traffic ratios are applied cleanly.
🧮 Holdout Group
When total traffic < 1.0, the remaining ratio is assigned to Holdout users —
they see your defaultVariant and are excluded from experiment analysis.
Example:
traffic: { A: 0.6, B: 0.2 } // 0.2 → Holdout👤 Setting User Context
Vuexp allows you to associate experiment tracking data with a specific user or visitor.
This helps distinguish between logged-in users and anonymous sessions when sending tracking events.
You can set the userContext at GlobalConfig; But its better to leave it to the plugin to generate a unique id for visitor
app.use(Vuexp, {
...
userId: undefined, //For logged-in users
visitorId: 'abc123', // Optional unique ID for guests
...
});You can update the userContext at any time — for example, right after user login or when visitor information becomes available.
import { setUserContext } from "@amirafa/vuexp";
// Example: set after authentication
setUserContext({
userId: 1000, // For logged-in users
visitorId: "abc123", // Optional unique ID for guests
});Behind the scenes, Vuexp keeps an internal reactive state used for tracking:
state.value = {
...
userId: string | undefined, // Logged-in user identifier
visitorId: string | undefined, // Anonymous visitor identifier
...
};userIdandvisitorIdare sent automatically with every tracked event once set.visitorIdis useful for anonymous visitors or guest sessions.- You can safely call
setUserContext()whenever user data becomes available — it will instantly update the tracking context used for all future events.
Example integration in a login flow:
// After successful login
login().then((user) => {
setUserContext({ userId: user.id });
});🧠 Tip: Call
setUserContext()once per session or whenever user identity changes (e.g., login, logout, or switch account).
🐞 Debug Mode
Set debug: true in your config to see internal logs:
[Vuexp] Experiment: experiment-button → Variant: B
[Vuexp] Track event: click { ...payload }Useful for development and QA environments.
💡 Best Practices
- Use consistent experiment naming (
header-color,cta-button, etc.) - Bump
versionwhenever changing traffic or variants - Keep traffic ratios between 0–1; Vuexp normalizes automatically
- Use
immediate: truefor purchase or form events - Enable remote config for dynamic updates without redeploys
- Use debug mode in staging to inspect assignments and payloads
📜 License
MIT © 2025 [Amirafa]
Simple. Reliable. Vue-native A/B Testing.
Changelog
[Version 1.0.7] - 2025-10-24
Feature
- Update
storing variantssystem
[Version 1.0.6] - 2025-10-21
Feature
- Obfuscate dist
[Version 1.0.5] - 2025-10-21
Feature
- Fix remote-config storage
- Obfuscate the experiments key/value
[Version 1.0.4] - 2025-10-20
Feature
- Add version to track payload.
- Update README.md (Add vuexp logo)
[Version 1.0.3] - 2025-10-20
Feature
- Add a feature that generates a unique visitor ID.
- Update README.md
[Version 1.0.2] - 2025-10-19
Feature
- Add Changelog to README.md
[Version 1.0.1] - 2025-10-19
Feature
- Add README.md documentation
- Add MIT Licence
[Version 1.0.0] - 2025-10-19
Feature
- Release package
