Estimated time: under 15 minutes.

Quick start

This walks through a complete integration in four steps — install, init, getVariant, track — using Next.js App Router. The SDK is framework-agnostic; any browser environment works the same way.

Before you start

You need a live experiment. In the dashboard:

  1. Create an experiment (Experiments → New) — name it, describe your control and variant, and define the conversion goal. It starts in draft.
  2. Open the experiment and Launch it. It must be live before the SDK will assign variants or record events.
  3. Copy its ID (the exp_… value in the URL and on the detail page).
  4. Copy your API key from Settings → API key. It looks like pk_live_… and is safe to expose in client code.

If the experiment isn't live

The config endpoint returns only a status for non-live experiments. The SDK then serves control to everyone and sends no impressions — so your dashboard counts stay at zero.

1. Install

terminal
npm install @absolutely-butter/sdk

The package has zero runtime dependencies and ships its own TypeScript types. pnpm add and yarn add work too.

2. Initialize

Call init() once, as early as possible on the client. It reads a cookie, and on a cache miss fetches the experiment config and assigns a variant. It returns a Promise<void> that resolves whether assignment succeeds or fails — it never rejects.

ts
import { init } from '@absolutely-butter/sdk'

init({
  apiKey: process.env.NEXT_PUBLIC_AB_API_KEY!, // your publishable pk_live_… key
  experimentId: 'exp_xxxxxxxxxxxx',            // from the experiment's detail page
  baseUrl: 'https://abserver-production.up.railway.app',
})

baseUrl is the origin of your Absolutely Butter API. timeout is optional and defaults to 2000 ms; if the config request is slower than that, the SDK gives up and serves control.

3. Render the variant

getVariant() is synchronous and returns 'control' or 'variant'. Before init() resolves — and on any failure — it returns 'control', so the original experience always renders by default.

ts
import { getVariant } from '@absolutely-butter/sdk'

const variant = getVariant() // 'control' | 'variant' — never throws, never null

if (variant === 'variant') {
  // render the change you're testing
} else {
  // render the original ('control' is also the value before init resolves)
}

4. Track the conversion

Call track('conversion') when the visitor completes your goal. It is fire-and-forget — it returns void, swallows all errors, and does nothing if the visitor has no assigned session (for example, a returning visitor whose session cookie was cleared).

tsx
import { track } from '@absolutely-butter/sdk'

<button onClick={() => track('conversion')}>Sign up</button>

Complete example

One client component that initializes the SDK, waits for ready(), renders the assigned variant, and tracks a conversion on click:

components/SignupCta.tsx
'use client'

import { useEffect, useState } from 'react'
import { init, ready, getVariant, track } from '@absolutely-butter/sdk'

const EXPERIMENT_ID = 'exp_xxxxxxxxxxxx'

export default function SignupCta() {
  const [variant, setVariant] = useState<'control' | 'variant'>('control')

  useEffect(() => {
    init({
      apiKey: process.env.NEXT_PUBLIC_AB_API_KEY!,
      experimentId: EXPERIMENT_ID,
      baseUrl: 'https://abserver-production.up.railway.app',
    })

    // ready() always resolves — on success or on silent failure
    ready().then(() => setVariant(getVariant()))
  }, [])

  return (
    <button
      className={variant === 'variant' ? 'btn-green' : 'btn-blue'}
      onClick={() => track('conversion')}
    >
      {variant === 'variant' ? 'Start your free trial' : 'Sign up'}
    </button>
  )
}

Verify it worked

Load the page in a browser, then open the experiment's detail screen in the dashboard:

  • Impressions increment on first assignment. Refreshing the same browser does not add more — impressions are deduplicated per session.
  • Conversions increment the first time that session fires track('conversion'). Repeat conversions from the same session are ignored.

Your integration is fully confirmed once the experiment is live and has recorded at least one impression and at least one conversion.

One thing to expect

Because assignment happens after the page loads, a visitor who ends up on variant sees control for a moment first, then a re-render. This is a control → variant flash, not a blank flash. The SDK reference covers two ways to handle it.