A/B Smartly - JavaScript/TypeScript SDK
The A/B Smartly JavaScript SDK is an isomorphic TypeScript library for Node.js (ESM and CommonJS) and browsers (IIFE).
- Node.js 18+ - uses native
fetchandAbortController - All modern browsers - Chrome, Firefox, Safari, Edge
Supported via optional polyfill injection. No extra dependencies are bundled - you provide your own:
import fetch from "node-fetch";
import { AbortController } from "abort-controller";
const sdk = new SDK({
endpoint: "https://sandbox.absmartly.io/v1",
apiKey: process.env.ABSMARTLY_API_KEY,
environment: "production",
application: "website",
fetchImpl: fetch,
AbortControllerImpl: AbortController,
});A pre-built legacy bundle transpiled to ES2015 is available at dist/index.legacy.js.
Important: This is an ES2015 build, not an ES5 build. It does not support IE10/IE11.
If you need legacy browser support, you must provide polyfills for missing APIs:
| API | Polyfill |
|---|---|
Promise |
es6-promise |
fetch |
whatwg-fetch |
AbortController |
abortcontroller-polyfill |
<!-- Polyfills (only needed for legacy browsers) -->
<script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.auto.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/whatwg-fetch@3/dist/fetch.umd.js"></script>
<script src="https://cdn.jsdelivr.net/npm/abortcontroller-polyfill@1/dist/abortcontroller-polyfill-only.js"></script>
<!-- SDK (legacy build) -->
<script src="https://unpkg.com/@absmartly/javascript-sdk/dist/index.legacy.js"></script>| File | Target | Use case |
|---|---|---|
dist/index.js |
ES2022 ESM | Modern bundlers (Vite, webpack, Rollup) |
dist/index.cjs |
ES2022 CJS | Node.js require() |
dist/index.global.js |
ES2022 IIFE | Modern browsers via <script> tag |
dist/index.legacy.js |
ES2015 IIFE | Older browsers that already support ES2015 syntax |
dist/index.d.ts |
TypeScript | Type declarations |
npm install @absmartly/javascript-sdk --saveimport { SDK } from "@absmartly/javascript-sdk";
// Or with CommonJS:
const { SDK } = require("@absmartly/javascript-sdk");<script src="https://unpkg.com/@absmartly/javascript-sdk/dist/index.global.js"></script>
<script>
const { SDK } = absmartly;
</script><!-- Include polyfills first (see Legacy Browsers section above) -->
<script src="https://unpkg.com/@absmartly/javascript-sdk/dist/index.legacy.js"></script>
<script>
const SDK = absmartly.SDK;
</script>Please follow the installation instructions before trying the following code:
This example assumes an Api Key, an Application, and an Environment have been created in the A/B Smartly web console.
const sdk = new SDK({
endpoint: "https://sandbox.absmartly.io/v1",
apiKey: process.env.ABSMARTLY_API_KEY,
environment: process.env.NODE_ENV,
application: process.env.APPLICATION_NAME,
});The application option can also be an object with name and version to track which version of your application is generating events. The version can be a number or a semver string:
const sdk = new SDK({
endpoint: "https://sandbox.absmartly.io/v1",
apiKey: process.env.ABSMARTLY_API_KEY,
environment: process.env.NODE_ENV,
application: { name: "website", version: "1.2.3" },
});| Option | Type | Required | Description |
|---|---|---|---|
endpoint |
string |
Yes | A/B Smartly API endpoint |
apiKey |
string |
Yes | API key from the web console |
environment |
string |
Yes | Environment name (e.g. "production") |
application |
string | { name, version } |
Yes | Application name or object |
agent |
string |
No | Custom agent identifier |
retries |
number |
No | Number of retries (default: 5) |
timeout |
number |
No | Request timeout in ms (default: 3000) |
keepalive |
boolean |
No | Enable keep-alive (default: true) |
fetchImpl |
typeof fetch |
No | Custom fetch implementation for legacy environments |
AbortControllerImpl |
typeof AbortController |
No | Custom AbortController for legacy environments |
client |
Client |
No | Custom HTTP client implementing the Client interface |
eventLogger |
EventLogger |
No | Custom event logger callback |
publisher |
ContextPublisher |
No | Custom context publisher |
provider |
ContextDataProvider |
No | Custom context data provider |
const context = sdk.createContext({
units: {
session_id: "5ebf06d8cb5d8137290c4abb64155584fbdb64d8",
},
});
context.ready().then(() => {
if (context.isFailed()) {
console.error("Context failed to initialize:", context.readyError());
// Context is still usable — all treatments return control (variant 0)
}
console.log("ABSmartly Context ready!");
});const context = sdk.createContext({
units: {
session_id: "5ebf06d8cb5d8137290c4abb64155584fbdb64d8",
},
});
await context.ready();
if (context.isFailed()) {
console.error("Context failed to initialize:", context.readyError());
// Context is still usable — all treatments return control (variant 0)
}Note:
ready()always resolves totrue, even when initialization fails. A failed context is still "ready" — it simply has no experiment data, so alltreatment()calls return0(control). UseisFailed()to check if initialization failed andreadyError()to inspect the error.
When doing full-stack experimentation with A/B Smartly, we recommend creating a context only once on the server-side. Creating a context involves a round-trip to the A/B Smartly event collector. We can avoid repeating the round-trip on the client-side by sending the server-side data embedded in the first document, for example, by rendering it on the template. Then we can initialize the A/B Smartly context on the client-side directly with it.
<head>
<script type="javascript">
const context = sdk.createContextWith(
{ units: { session_id: "5ebf06d8cb5d8137290c4abb64155584fbdb64d8" } },
{{ serverSideContext.data() }}
);
</script>
</head>You can add additional units to a context by calling the unit() or the units() method.
This method may be used for example, when a user logs in to your application, and you want to use the new unit type to the context.
Please note that you cannot override an already set unit type as that would be a change of identity, and will throw an exception. In this case, you must create a new context instead.
The unit() and units() methods can be called before the context is ready.
context.unit("db_user_id", 1000013);
// or
context.units({
db_user_id: 1000013,
});The attribute() and attributes() methods can be called before the context is ready.
context.attribute("user_agent", navigator.userAgent);
context.attributes({
customer_age: "new_customer",
});You can opt in to automatically include system attributes (SDK name, SDK version, application, environment, and application version) in every publish payload. These are sent as context attributes and can be useful for debugging and filtering in the Web Console.
To enable this, set the includeSystemAttributes option to true when creating the context:
const context = sdk.createContext(request, {
includeSystemAttributes: true,
});When enabled, the following attributes are automatically prepended to the publish request payload:
| Attribute | Description |
|---|---|
sdk_name |
The SDK agent name (e.g. "absmartly-javascript-sdk") |
sdk_version |
The SDK version (e.g. "2.0.0") |
application |
The application name from the SDK configuration |
environment |
The environment from the SDK configuration |
app_version |
The application version, only included if greater than 0 |
These system attributes are prepended before any user-defined attributes.
if (context.treatment("exp_test_experiment") === 0) {
// user is in control group (variant 0)
} else {
// user is in treatment group
}Experiment variables allow you to configure per-variant values directly from the A/B Smartly web console.
const buttonColor = context.variableValue("button.color", "grey"); // "grey" is the defaultUse peekVariableValue() to access a variable without triggering an exposure:
const buttonColor = context.peekVariableValue("button.color", "grey");Goals are created in the A/B Smartly web console.
context.track("payment", { item_count: 1, total_amount: 1999.99 });Sometimes it is necessary to ensure all events have been published to the A/B Smartly collector, before proceeding.
One such case is when the user is about to navigate away right before being exposed to a treatment.
You can explicitly call the publish() method, which returns a promise, before navigating away.
await context.publish();
window.location = "https://www.absmartly.com";The finalize() method will ensure all events have been published to the A/B Smartly collector, like publish(), and will also "seal" the context, throwing an error if any method that could generate an event is called.
await context.finalize();
window.location = "https://www.absmartly.com";For long-running single-page-applications (SPA), the context is usually created once when the application is first reached.
However, any experiments being tracked in your production code, but started after the context was created, will not be triggered.
To mitigate this, we can use the refreshPeriod option when creating the context.
const context = sdk.createContext(
{ units: { session_id: "5ebf06d8cb5d8137290c4abb64155584fbdb64d8" } },
{ refreshPeriod: 5 * 60 * 1000 },
);Alternatively, the refresh() method can be called manually.
The refresh() method pulls updated experiment data from the A/B Smartly collector and will trigger recently started experiments when treatment() is called again.
setTimeout(async () => {
try {
await context.refresh();
} catch (error) {
console.error(error);
}
}, 5 * 60 * 1000);The A/B Smartly SDK can be instantiated with an event logger used for all contexts.
In addition, an event logger can be specified when creating a particular context, in the createContext call options.
The example below illustrates this with the implementation of the default event logger, used if none is specified.
const sdk = new SDK({
endpoint: "https://sandbox-api.absmartly.com/v1",
apiKey: process.env.ABSMARTLY_API_KEY,
environment: process.env.NODE_ENV,
application: process.env.APPLICATION_NAME,
eventLogger: (context, eventName, data) => {
if (eventName === "error") {
console.error(data);
}
},
});The data parameter depends on the type of event. Currently, the SDK logs the following events:
| eventName | when | data |
|---|---|---|
"error" |
Context receives an error |
error object thrown |
"ready" |
Context turns ready |
data used to initialize the context |
"refresh" |
Context.refresh() method succeeds |
data used to refresh the context |
"publish" |
Context.publish() method succeeds |
data sent to the A/B Smartly event collector |
"exposure" |
Context.treatment() method succeeds on first exposure |
exposure data enqueued for publishing |
"goal" |
Context.track() method succeeds |
goal data enqueued for publishing |
"finalize" |
Context.finalize() method succeeds the first time |
undefined |
Note: The event logger is wrapped in a try/catch by the SDK. A broken logger will not crash SDK operations.
Although generally not recommended, it is sometimes necessary to peek at a treatment without triggering an exposure.
The A/B Smartly SDK provides a peek() method for that.
if (context.peek("exp_test_experiment") === 0) {
// user is in control group (variant 0)
} else {
// user is in treatment group
}During development, for example, it is useful to force a treatment for an experiment. This can be achieved with the override() and/or overrides() methods.
The override() and overrides() methods can be called before the context is ready.
context.override("exp_test_experiment", 1); // force variant 1 of treatment
context.overrides({
exp_test_experiment: 1,
exp_another_experiment: 0,
});Experiments can have custom field values configured in the A/B Smartly web console.
const keys = context.customFieldKeys();
const value = context.customFieldValue("exp_test_experiment", "country");
const type = context.customFieldValueType("exp_test_experiment", "country");The SDK provides typed error classes for programmatic error handling:
import {
ABSmartlyError,
ContextNotReadyError,
ContextFinalizedError,
TimeoutError,
RetryError,
} from "@absmartly/javascript-sdk";
try {
context.treatment("exp_test");
} catch (error) {
if (error instanceof ContextNotReadyError) {
// Context not ready yet — await context.ready() first
} else if (error instanceof ContextFinalizedError) {
// Context has been finalized — create a new one
}
}You can provide your own HTTP client implementation by implementing the Client interface:
import type { Client } from "@absmartly/javascript-sdk";
class MyCustomClient implements Client {
async getContext(options?) { /* ... */ }
async publish(params, options?) { /* ... */ }
getAgent() { return "my-custom-client"; }
getApplication() { return { name: "my-app", version: "1.0.0" }; }
getEnvironment() { return "production"; }
}
const sdk = new SDK({
client: new MyCustomClient(),
// No need for endpoint, apiKey, etc. when providing your own client
});Similarly, ContextDataProvider and ContextPublisher interfaces can be implemented for custom data fetching and publishing strategies.
It is possible to set a timeout per individual HTTP request, overriding the global timeout set for all request when instantiating the SDK object.
Here is an example of setting a timeout only for the createContext request.
const context = sdk.createContext(request, {
refreshPeriod: 5 * 60 * 1000,
}, {
timeout: 1500,
});Sometimes it is useful to cancel an inflight HTTP request, for example, when the user is navigating away. The A/B Smartly SDK supports cancellation via an AbortSignal.
const controller = new AbortController();
const context = sdk.createContext(request, {
refreshPeriod: 5 * 60 * 1000,
}, {
signal: controller.signal,
});
// abort request if not ready after 1500ms
const timeoutId = setTimeout(() => controller.abort(), 1500);
await context.ready();
clearTimeout(timeoutId);- Named exports instead of default export:
import { SDK } from "@absmartly/javascript-sdk"instead ofimport absmartly from "@absmartly/javascript-sdk" - Provider/Publisher classes renamed:
ContextDataProvider→DefaultContextDataProvider,ContextPublisher→DefaultContextPublisher. The old names are now interfaces. Goaltype renamed toGoalAchievementfor clarity- Node.js 14+ minimum (was Node.js 6+)
- IE10 and IE11 are not supported by the shipped bundles -
index.legacy.jsis ES2015, not ES5. Supporting IE10/IE11 would require an additional ES5 build plus polyfills. - No bundled polyfills -
core-js,node-fetch, andrfdcare no longer bundled. Legacy environments must provide polyfills explicitly. - Browser bundle renamed -
dist/absmartly.min.jsis nowdist/index.global.js(modern) ordist/index.legacy.js(ES2015 legacy build)
- Full TypeScript support with type declarations
- Zero runtime dependencies
- Optional polyfill injection (
fetchImpl,AbortControllerImpl) - ESM, CJS, and IIFE builds from a single source
- Smaller bundle size
- Interface-based dependency injection (
Client,ContextDataProvider,ContextPublisher) - Domain error classes (
ABSmartlyError,ContextNotReadyError,ContextFinalizedError) readyError()method to inspect initialization failures- Input validation on all public methods
AbortControlleris no longer exported by the SDK package.- Use the platform/global
AbortControllerinstead. - In legacy environments, provide your own polyfill and pass it via
AbortControllerImpl. NormalizedClientOptionsis no longer exported (internal type).
A/B Smartly is the leading provider of state-of-the-art, on-premises, full-stack experimentation platforms for engineering and product teams that want to confidently deploy features as fast as they can develop them. A/B Smartly's real-time analytics helps engineering and product teams ensure that new features will improve the customer experience without breaking or degrading performance and/or business metrics.