Headless Experiments

Run A/B experiments in React, Vue, Next.js, and other framework-owned UIs. Ours Privacy handles assignment, sticky bucketing, and impression tracking while your code renders the variant.

Headless Experiments

A headless experiment is the application-owned rendering pattern for Ours Privacy A/B testing.

Use it when you want Ours Privacy to handle:

  • visitor assignment
  • sticky bucketing
  • impression tracking
  • experiment exposure to your application code

...while your application code decides what to render.

This is the best fit for React, Vue, Next.js, and other framework-controlled UIs where direct DOM mutation is inherently best-effort.


What It Is

A headless experiment is a usage pattern on top of the JavaScript SDK. The runtime gives you the pieces you need:

  • window.ours_experiments.getExperiment(experimentId)
  • window.ours_experiments.getExperimentByKey(experimentKey)
  • window.ours_experiments.getExperiments()
  • window.ours_experiments.getVariant(experimentId)
  • window.ours_experiments.getAssignments()
  • window.ours_experiments.on('assigned', callback)

Your application reads the assignment and renders the appropriate UI itself.


When To Use It

Use a headless experiment when:

  • your framework re-renders the same subtree after hydration or state changes
  • the same experiment assignment needs to control multiple components
  • you want type-safe rendering logic in application code
  • you want personalization and experiments to use the same app-owned rendering path

Prefer content experiments when the page is mostly static and the runtime can safely mutate the HTML directly. Prefer redirect experiments when you've built two distinct pages.


Use The CDP Visitor ID

Experimentation should use the same visitor ID as the Ours Web SDK.

Do not generate your own visitor ID for headless rendering. Read the current visitor ID from the CDP and pass that into window.ours_experiments.init(...) when you are manually initializing the experiments runtime.

If you already load experimentation through the Web SDK with experimentation.token, the Web SDK handles this wiring for you automatically.

If you are manually initializing the experiments runtime alongside the Web SDK, use the CDP visitor ID:

const visitorId = ours.getVisitorId();

window.ours_experiments.init({
  visitorId,
});

If you are working in a script-tag environment where window.ours_data is already available, this is equivalent:

window.ours_experiments.init({
  visitorId: window.ours_data?.visitor_id,
});

See Web SDK (client) for the CDP-side getVisitorId() API.


For most sites, load experimentation through the Web SDK:

ours('init', 'YOUR_CDP_TOKEN', {
  experimentation: {
    token: 'YOUR_EXPERIMENT_TOKEN',
  },
});

That keeps CDP identity and experimentation identity aligned automatically.

Then render from your app code using window.ours_experiments.


A/B Example

const heroExperiment = window.ours_experiments.getExperimentByKey('homepage-hero');

if (heroExperiment?.variantId === 'var_enterprise') {
  renderEnterpriseHero();
} else {
  renderDefaultHero();
}

This lets the experiment runtime own assignment while your application owns the rendered components.


React / SPA Pattern

In single-page apps, initialize experimentation once, then read assignments during render or state setup.

window.ours_experiments.init({
  visitorId: ours.getVisitorId(),
});

const heroExperiment = window.ours_experiments.getExperimentByKey('homepage-hero');

if (heroExperiment?.variantId === 'var_enterprise') {
  renderEnterpriseHero();
} else {
  renderDefaultHero();
}

window.ours_experiments.on('assigned', ({ experimentId, variantId }) => {
  if (experimentId === 'exp_homepage_hero' && variantId === 'var_enterprise') {
    renderEnterpriseHero();
  }
});

Use the direct getExperiment(...) read for current state, and the 'assigned' event when you need to react to initialization or SPA re-evaluation.


Personalization

For app-owned personalization that doesn't run a statistical test, see Headless Personalization. It uses the same SDK shape — read visitor signals from getVisitorContext() and branch in your own code.


Conversion Events

Headless experiments use the same ordinary CDP events as every other experiment flow.

If your experiment metric is signup_completed, track that event the normal way:

function onSignupComplete() {
  ours('track', 'signup_completed');
}

There is no short experiment-specific conversion API documented here. The experiment runtime records impressions, and your existing CDP events remain the source of truth for conversions.


Assign a stable experiment key in the dashboard and read by that key in application code. This gives you a cleaner contract than hard-coding raw experiment IDs:

  • getExperimentByKey('homepage-hero')
  • getExperiments() when you want the whole active set
  • getExperiment(experimentId) when you already have a specific ID from another system

Variant IDs are the assignment values you branch on, so most teams keep experiment keys in code and compare against known variant IDs from the experiment.


Next Steps

How is this guide?

On this page