---
title: Experiments
description: Discover Storyblok's documentation with comprehensive developer guides, user manuals, API references, and examples to help you get the most out of the headless CMS platform.
url: https://storyblok.com/docs/concepts/experiments
---

# Experiments

Storyblok Experiments introduces content variants at the story level. Use experiments for A/B testing or to manage personalized content.

> [!NOTE]
> Experiments is a premium feature. Learn more on the [pricing page](/pricing).

To learn how to set up experiments and link stories from the Visual Editor, follow the [Experiments manual](/docs/manuals/experiments).

## How experiments work

An experiment record connects a set of stories to a set of variants. Each variant accepts a name, a technical name, a weight distribution (as a percentage), a flag indicating whether it's the control, and a list of story mappings that pair a source story with the variant story that replaces it.

The control is the variant designated as the baseline. Always name and treat the main story as the control. The experiments endpoint flags the main story with `is_control: true`, which maintains consistency between the UI and the API.

When a site visitor loads a story associated with an experiment, the logic you implemented in your code picks one variant according to the configured weights, and then renders the variant’s mapped story.

## Integrate experiments

To integrate experiments into the code, follow these steps:

1.  Fetch a story.
2.  Fetch experiments and determine whether any active experiment targets the current story.
3.  Pick a variant based on the variants’ weights, respecting the visitor's prior assignment.
4.  Fetch the variant story referenced in the variant’s story mappings and render it.
5.  Persist the chosen variant for the visitor.
6.  Push the experiment's analytics data to Storyblok regularly.

### Fetch experiments

Retrieve the list of active experiments using the [Content Delivery API's experiments endpoint](#).

Example API response

```json
{
  "experiments": [
    {
      "id": 176070002766742,
      "name": "a_simple_experiment",
      "display_name": "A simple experiment",
      "story_ids": [176024833123843],
      "variants": [
        {
          "name": "control",
          "display_name": "Control",
          "public_id": "var_uhjedoghzx68",
          "weight": 70,
          "is_control": true,
          "story_mappings": []
        },
        {
          "name": "test",
          "display_name": "Test",
          "public_id": "var_xhicas8uin5l",
          "weight": 30,
          "is_control": false,
          "story_mappings": [
            {
              "original_story_id": 176024833123843,
              "original_slug": "home",
              "variant_story_id": 176070141098149,
              "variant_slug": "home-a_simple_experiment-test"
            }
          ]
        }
      ]
    }
  ],
  "cv": 1778675529
}
```

The following example determines whether an active experiment includes the current story:

```javascript
import { storyblokInit, apiPlugin } from '@storyblok/js';

const { storyblokApi } = storyblokInit({
  accessToken: 'YOUR_DELIVERY_API_TOKEN',
  use: [apiPlugin],
});

// Fetch story
const { data: storyData } = await storyblokApi.get('cdn/stories/home', {
  version: 'published',
});

let story = storyData.story;

// Fetch experiments
const { data: experimentsData } = await storyblokApi.get('cdn/experiments');

const experiments = experimentsData.experiments;

// Determine whether an active experiment includes the fetched story
const experiment = experiments.find((experiment) =>
  experiment.story_ids.includes(story.id),
);
```

### Choose a variant

Set the variant weights in the Storyblok UI. The sum of all distribution percentages must equal 100%. and sum to `100`. A draw against a uniform random number in that range maps to a single variant. Consider the following example:

```javascript
const pickVariant = (variants) => {
  const total = variants.reduce((acc, variant) => acc + variant.weight, 0);
  let random = Math.random() * total;
  for (const variant of variants) {
    random -= variant.weight;
    if (random <= 0) return variant;
  }
  return variants[variants.length - 1];
};
```

You can implement similar experimentation logic on the server or at the edge.

### Render a variant

When the chosen variant is the control, render the main story as-is.

When the chosen variant is another variant, look up the entry in the variant’s story mappings that matches the current story’s `full_slug`.

Fetch that variant story from the Content Delivery API, and render it instead of the main story:

```javascript
if (experiment) {
  const chosenVariant = pickVariant(experiment.variants);

  // Unless the chosen variant is the control, its slug differs from the main story
  if (!chosenVariant.is_control) {
    const mapping = chosenVariant.story_mappings.find(
      (mapping) => mapping.original_slug === story.full_slug,
    );
    if (mapping) {
      // Fetch the variant story
      const { data: variantData } = await storyblokApi.get(
        `cdn/stories/${mapping.variant_slug}`,
        { version: 'published' },
      );
      // Overwrite the main story data with the variant story data
      story = variantData.story;
    }
  }
}
```

> [!NOTE]
> A variant story’s slug combines the main story’s slug, the experiment’s technical name, and the variant’s technical name, joined by hyphens. For example, a `home` story in an experiment with the technical name `hero-test` and a `bold` variant resolves to a variant story at `home-hero-test-bold`.

### Persist a variant

To measure consistent behavior over time, a visitor who loads the same story should receive the same variant.

A common method is to store the variant ID in a cookie keyed by the experiment ID. This persists the assignment per visitor and per experiment, not per story. Subsequent requests reuse the cookie to render the associated story’s variant.

### Webhooks

Experiments provide the following webhook triggers:

-   Experiment created
-   Experiment started
-   Experiment paused
-   Experiment resumed
-   Experiment winner selected
-   Experiment completed
-   Experiment deleted

Learn how to configure webhooks and explore example payloads in the [webhooks concept](/docs/concepts/webhooks).

Use webhooks for actions like toggling experimentation logic, clearing the cache, or rebuilding the site.

## View results

Use an analytics platform to collect performance data for an _active_ experiment and load it into Storyblok with the Management API’s [experiments results endpoint](/docs/api/management/experiments/push-experiment-results).

Storyblok displays the data in **Labs** → **Experiments** → **Experiment name** → **Results**, where editors can monitor it without leaving the CMS.

## Further resources

[User Manual: Experiment](/docs/manuals/experiments)

[Content Delivery API Reference: Experiments](#)

[Management API Reference: Experiments](#)

[Developer Concept: Webhooks](/docs/concepts/webhooks)

  

## Pagination

-   [Previous: E-commerce](/docs/concepts/e-commerce)
-   [Next: Fields](/docs/concepts/fields)
