Experiment Assignment & IDs
How Coframe assigns visitors to experiments and variants, and what the treatmentsDataV2 callback returns
Copy this guide and your current settings as a prompt for your AI agent.
Preview agent promptSee the guide, code and settings included when you copy.
This page explains how Coframe assigns each visitor to an experiment and variant, what determines whether they see the original page or a treatment, and what the identifiers in the treatmentsDataV2 callback mean and how they relate to one another.
On this page
01The treatmentsDataV2 Callback
When a visitor is assigned to an experiment, Coframe calls the treatmentsDataV2 callback with a payload containing eight fields. Understanding these fields is the foundation for using Coframe data in your own analytics and attribution pipelines.
| Field | Type | Description |
|---|---|---|
coframe_experiment_id |
string | Experiment ID shared by all visitors (baseline + treatment) in the same experiment |
coframe_experiment_slug |
string | Human-readable experiment name |
coframe_variant_id |
string | The specific variant (or holdout) assigned to this visitor |
coframe_variant_slug |
string | Human-readable variant name |
coframe_user_token |
string | Unique visitor identifier (persistent across visits) |
coframe_impression_id |
string | Unique ID for this specific page view / impression |
coframe_in_global_holdout |
boolean | true if the visitor is excluded from all experiments (Step 1 holdout) |
coframe_in_exp_baseline |
boolean | true if the visitor is in the experiment baseline (Step 2 holdout) |
The TypeScript type for this payload:
type CoframeTreatmentDataV2 = {
coframe_experiment_id: string;
coframe_experiment_slug: string;
coframe_variant_id: string;
coframe_variant_slug: string;
coframe_user_token: string;
coframe_impression_id: string;
coframe_in_global_holdout: boolean;
coframe_in_exp_baseline: boolean;
};02How a Visitor Gets Assigned
Assignment follows a three-step gate. A visitor must pass each step to reach the next.
| Step | Gate | Default split | If passed |
|---|---|---|---|
| 1. Global Holdout | project-wide | 5% holdout / 95% pass | continue to Step 2 |
| 2. Experiment Holdout | per-experiment | e.g. 50% baseline / 50% treatment | continue to Step 3 |
| 3. Variant Selection | weighted traffic | e.g. 33/33/34 across variants | sees that variant |
Each step is a gate — a visitor filtered out at any step never reaches the next.
Seats and iterations: By default a visitor’s seat changes hourly when a new experiment iteration begins; each new seat re-runs the assignment logic, so a visitor may enter or leave the treatment group between iterations. Sticky seats keep the same variant assignment for the duration of the experiment, regardless of iterations. The global-holdout seat is permanent for the lifetime of the project — a visitor placed in the global holdout stays there.
03Experiment Entity Hierarchy
The assignment model is a tree. Each level maps to one or more identifiers in the callback payload.
Global Experiment (coframe_experiment_id)
├─ Global Holdout (coframe_in_global_holdout: true)
└─ Global Treatment (coframe_in_global_holdout: false)
└─ Frames (Home / Product / Cart …)
└─ Audiences (Global / Desktop / Mobile — each its own holdout experiment)
└─ Experiment Holdout
├─ Baseline (coframe_in_exp_baseline: true)
└─ Treatment (coframe_variant_id)
└─ Experiment (coframe_experiment_id)
└─ Variants v0 / v1 / v2 / v3 (coframe_variant_id)Level 1 — Global Experiment. A single top-level experiment that wraps the entire project. It controls the global holdout split: a small percentage of all visitors are assigned to the global holdout and see no Coframe experiments at all. Their presence allows clean-room measurement of Coframe’s aggregate effect on the site.
Level 2 — Frames. A frame is a named region of the page — for example, homepage-hero, product-description, or checkout-cta. Frames are the units of experimentation. Each frame can run one active experiment at a time for any given visitor.
Level 3 — Audiences. Within a frame, each audience (Global, Desktop, Mobile, or a custom segment) runs as its own sub-experiment. A visitor is matched to the first audience whose targeting conditions they satisfy. Unmatched visitors skip the frame entirely.
Level 4 — Experiment Holdout. Once an audience is matched, the visitor enters the experiment holdout step. This is a per-experiment split that separates the baseline group (visitors who see the original content but are tracked for comparison) from the treatment group (visitors eligible to see a variant). The coframe_in_exp_baseline field reflects this split.
Level 5 — Experiment. The experiment is the container for a set of variants running against the baseline. Baseline and all treatment visitors share the same coframe_experiment_id, which is what makes cross-group comparison possible.
Level 6 — Variant Selection. Treatment-eligible visitors are assigned to a specific variant. The coframe_variant_id identifies the assigned variant. Traffic can be split evenly or with custom weights across any number of variants.
04Frames with Multiple Experiments
A frame can have multiple experiments configured, each targeting a different audience. When a visitor arrives, Coframe evaluates the audiences in order and stops at the first match. Only one experiment per frame activates for any single visitor.
| Visitor | Exp1 (Desktop) | Exp2 (Mobile) | Result |
|---|---|---|---|
| Desktop user | Matches | Skipped | Enters Exp1’s holdout/variant tree |
| Mobile user | Doesn’t match | Matches | Enters Exp2’s holdout/variant tree |
| Tablet user | Doesn’t match | Doesn’t match | No experiment activates |
Four rules govern this behavior:
- Each experiment targets exactly one audience.
- The first matching audience wins; subsequent audiences are not evaluated.
- Only one experiment per frame activates per visitor per page view.
- Parallel experimentation is possible across different frames simultaneously, but not within a single frame.
05How Baseline and Treatment Visitors Are Grouped
Both baseline and treatment visitors share the same coframe_experiment_id. This is intentional — it allows them to be analyzed together in the same experiment report. Use coframe_variant_id and coframe_in_exp_baseline to tell them apart.
| Visitor group | coframe_experiment_id |
coframe_variant_id |
coframe_in_exp_baseline |
|---|---|---|---|
| Baseline | abc...01 |
abc...02 (baseline variant) |
true |
| Treatment — Variant A | abc...01 |
def...05 |
false |
| Treatment — Variant B | abc...01 |
def...06 |
false |
| Global Holdout | zzz...99 (holdout experiment) |
zzz...98 |
false |
The global holdout is not part of the experiment, so its coframe_experiment_id is different from the one shared by baseline and treatment visitors.
06Callback Examples
The following examples show the exact payload shape for the three visitor outcomes.
Visitor 1 — Treatment (sees Variant A): passed both holdouts, assigned to Variant A.
{
"coframe_experiment_id": "abc...01",
"coframe_experiment_slug": "homepage-headline",
"coframe_variant_id": "def...05",
"coframe_variant_slug": "homepage-headline-v1",
"coframe_user_token": "usr_a1b2c3",
"coframe_impression_id": "imp_x1y2z3",
"coframe_in_global_holdout": false,
"coframe_in_exp_baseline": false
}Visitor 2 — Baseline: sees the original page and is tracked for comparison.
{
"coframe_experiment_id": "abc...01",
"coframe_experiment_slug": "homepage-headline",
"coframe_variant_id": "abc...02",
"coframe_variant_slug": "homepage-headline",
"coframe_user_token": "usr_d4e5f6",
"coframe_impression_id": "imp_a7b8c9",
"coframe_in_global_holdout": false,
"coframe_in_exp_baseline": true
}Visitor 3 — Global Holdout: excluded from all experiments. Note the different coframe_experiment_id — it identifies the global holdout group, not the homepage-headline experiment.
{
"coframe_experiment_id": "zzz...99",
"coframe_experiment_slug": "global-holdout",
"coframe_variant_id": "zzz...98",
"coframe_variant_slug": "global-holdout",
"coframe_user_token": "usr_g7h8i9",
"coframe_impression_id": "imp_d1e2f3",
"coframe_in_global_holdout": true,
"coframe_in_exp_baseline": false
}Key observations: Visitors 1 and 2 share the same coframe_experiment_id (abc...01), which groups baseline and treatment together for analysis. Visitor 3 has a different coframe_experiment_id (zzz...99) because they were excluded before reaching any experiment.
07Dashboard vs. Callback IDs
In the Coframe dashboard, baseline and treatment variants appear side by side in the same experiment report. They are unified by their shared coframe_experiment_id. This is why the dashboard can show baseline conversion rates alongside each treatment variant — they all belong to the same experiment container.
The key distinction is that baseline and treatment are assigned at different steps in the gate logic (Step 2 vs. Step 3), but both are captured under the same coframe_experiment_id. When connecting Coframe data to an external analytics tool, always use coframe_experiment_id from the callback as the primary grouping key. Do not attempt to reconstruct experiment membership from coframe_variant_id alone, because that field identifies the specific variant, not the experiment as a whole.
08Using Callback Data in Analytics
When forwarding treatmentsDataV2 data to an external analytics platform:
- Group visitors by
coframe_experiment_idto assemble the full experiment population (baseline + all treatment variants). - Identify which variant each visitor saw using
coframe_variant_idandcoframe_variant_slug. - Separate the baseline group from treatment groups using
coframe_in_exp_baseline: true. - Identify visitors excluded from all experiments using
coframe_in_global_holdout: true; they can be used for a clean holdout comparison. - Use
coframe_user_tokenfor visitor-level attribution and cross-session stitching. - Use
coframe_impression_idfor impression-level deduplication when the same visitor sees the same experiment on multiple page views.
09Summary
| Concept | Key point |
|---|---|
| 3-step assignment | Global holdout → experiment holdout → variant selection. Each step is a gate; visitors who don’t pass are not assigned downstream. |
coframe_experiment_id |
Shared by all visitors in the same experiment, including baseline. Use it to group populations for analysis. |
| Multiple experiments per frame | Targeting is audience-based; only the first matching audience’s experiment activates per visitor per frame. |
| Deterministic assignment | A visitor’s seat is determined by a hash of their coframe_user_token, so the same visitor always lands in the same bucket within an iteration. |
# Experiment Assignment & IDs
How Coframe assigns visitors to experiments and variants, and what the treatmentsDataV2 callback returns
This page explains how Coframe assigns each visitor to an experiment and variant, what determines whether they see the original page or a treatment, and what the identifiers in the `treatmentsDataV2` callback mean and how they relate to one another.
[Video: Video: the eight callback fields, three example visitor payloads, and which IDs to use in analytics (70s, narrated)](assets/videos/fig07-experiment-ids.mp4)
Watch: 70s — the eight callback fields, three example visitor payloads, and which IDs to use in analytics
## The treatmentsDataV2 Callback
When a visitor is assigned to an experiment, Coframe calls the `treatmentsDataV2` callback with a payload containing eight fields. Understanding these fields is the foundation for using Coframe data in your own analytics and attribution pipelines.
Field |
Type |
Description |
`coframe_experiment_id` |
string |
Experiment ID shared by all visitors (baseline + treatment) in the same experiment |
`coframe_experiment_slug` |
string |
Human-readable experiment name |
`coframe_variant_id` |
string |
The specific variant (or holdout) assigned to this visitor |
`coframe_variant_slug` |
string |
Human-readable variant name |
`coframe_user_token` |
string |
Unique visitor identifier (persistent across visits) |
`coframe_impression_id` |
string |
Unique ID for this specific page view / impression |
`coframe_in_global_holdout` |
boolean |
`true` if the visitor is excluded from all experiments (Step 1 holdout) |
`coframe_in_exp_baseline` |
boolean |
`true` if the visitor is in the experiment baseline (Step 2 holdout) |
The TypeScript type for this payload:
TS
```
type CoframeTreatmentDataV2 = {
coframe_experiment_id: string;
coframe_experiment_slug: string;
coframe_variant_id: string;
coframe_variant_slug: string;
coframe_user_token: string;
coframe_impression_id: string;
coframe_in_global_holdout: boolean;
coframe_in_exp_baseline: boolean;
};
```
## How a Visitor Gets Assigned
Assignment follows a three-step gate. A visitor must pass each step to reach the next.
Step |
Gate |
Default split |
If passed |
1. Global Holdout |
project-wide |
5% holdout / 95% pass |
continue to Step 2 |
2. Experiment Holdout |
per-experiment |
e.g. 50% baseline / 50% treatment |
continue to Step 3 |
3. Variant Selection |
weighted traffic |
e.g. 33/33/34 across variants |
sees that variant |
Each step is a gate — a visitor filtered out at any step never reaches the next.
Seats and iterations: By default a visitor’s seat changes hourly when a new experiment iteration begins; each new seat re-runs the assignment logic, so a visitor may enter or leave the treatment group between iterations. Sticky seats keep the same variant assignment for the duration of the experiment, regardless of iterations. The global-holdout seat is permanent for the lifetime of the project — a visitor placed in the global holdout stays there.
## Experiment Entity Hierarchy
The assignment model is a tree. Each level maps to one or more identifiers in the callback payload.
PLAINTEXT
```
Global Experiment (coframe_experiment_id)
├─ Global Holdout (coframe_in_global_holdout: true)
└─ Global Treatment (coframe_in_global_holdout: false)
└─ Frames (Home / Product / Cart …)
└─ Audiences (Global / Desktop / Mobile — each its own holdout experiment)
└─ Experiment Holdout
├─ Baseline (coframe_in_exp_baseline: true)
└─ Treatment (coframe_variant_id)
└─ Experiment (coframe_experiment_id)
└─ Variants v0 / v1 / v2 / v3 (coframe_variant_id)
```
Level 1 — Global Experiment. A single top-level experiment that wraps the entire project. It controls the global holdout split: a small percentage of all visitors are assigned to the global holdout and see no Coframe experiments at all. Their presence allows clean-room measurement of Coframe’s aggregate effect on the site.
Level 2 — Frames. A frame is a named region of the page — for example, `homepage-hero`, `product-description`, or `checkout-cta`. Frames are the units of experimentation. Each frame can run one active experiment at a time for any given visitor.
Level 3 — Audiences. Within a frame, each audience (Global, Desktop, Mobile, or a custom segment) runs as its own sub-experiment. A visitor is matched to the first audience whose targeting conditions they satisfy. Unmatched visitors skip the frame entirely.
Level 4 — Experiment Holdout. Once an audience is matched, the visitor enters the experiment holdout step. This is a per-experiment split that separates the baseline group (visitors who see the original content but are tracked for comparison) from the treatment group (visitors eligible to see a variant). The `coframe_in_exp_baseline` field reflects this split.
Level 5 — Experiment. The experiment is the container for a set of variants running against the baseline. Baseline and all treatment visitors share the same `coframe_experiment_id`, which is what makes cross-group comparison possible.
Level 6 — Variant Selection. Treatment-eligible visitors are assigned to a specific variant. The `coframe_variant_id` identifies the assigned variant. Traffic can be split evenly or with custom weights across any number of variants.
## Frames with Multiple Experiments
A frame can have multiple experiments configured, each targeting a different audience. When a visitor arrives, Coframe evaluates the audiences in order and stops at the first match. Only one experiment per frame activates for any single visitor.
Visitor |
Exp1 (Desktop) |
Exp2 (Mobile) |
Result |
Desktop user |
Matches |
Skipped |
Enters Exp1’s holdout/variant tree |
Mobile user |
Doesn’t match |
Matches |
Enters Exp2’s holdout/variant tree |
Tablet user |
Doesn’t match |
Doesn’t match |
No experiment activates |
Four rules govern this behavior:
- Each experiment targets exactly one audience.
- The first matching audience wins; subsequent audiences are not evaluated.
- Only one experiment per frame activates per visitor per page view.
- Parallel experimentation is possible across different frames simultaneously, but not within a single frame.
## How Baseline and Treatment Visitors Are Grouped
Both baseline and treatment visitors share the same `coframe_experiment_id`. This is intentional — it allows them to be analyzed together in the same experiment report. Use `coframe_variant_id` and `coframe_in_exp_baseline` to tell them apart.
Visitor group |
`coframe_experiment_id` |
`coframe_variant_id` |
`coframe_in_exp_baseline` |
Baseline |
`abc...01` |
`abc...02` (baseline variant) |
`true` |
Treatment — Variant A |
`abc...01` |
`def...05` |
`false` |
Treatment — Variant B |
`abc...01` |
`def...06` |
`false` |
Global Holdout |
`zzz...99` (holdout experiment) |
`zzz...98` |
`false` |
The global holdout is not part of the experiment, so its `coframe_experiment_id` is different from the one shared by baseline and treatment visitors.
## Callback Examples
The following examples show the exact payload shape for the three visitor outcomes.
Visitor 1 — Treatment (sees Variant A): passed both holdouts, assigned to Variant A.
JSON
```
{
"coframe_experiment_id": "abc...01",
"coframe_experiment_slug": "homepage-headline",
"coframe_variant_id": "def...05",
"coframe_variant_slug": "homepage-headline-v1",
"coframe_user_token": "usr_a1b2c3",
"coframe_impression_id": "imp_x1y2z3",
"coframe_in_global_holdout": false,
"coframe_in_exp_baseline": false
}
```
Visitor 2 — Baseline: sees the original page and is tracked for comparison.
JSON
```
{
"coframe_experiment_id": "abc...01",
"coframe_experiment_slug": "homepage-headline",
"coframe_variant_id": "abc...02",
"coframe_variant_slug": "homepage-headline",
"coframe_user_token": "usr_d4e5f6",
"coframe_impression_id": "imp_a7b8c9",
"coframe_in_global_holdout": false,
"coframe_in_exp_baseline": true
}
```
Visitor 3 — Global Holdout: excluded from all experiments. Note the different `coframe_experiment_id` — it identifies the global holdout group, not the homepage-headline experiment.
JSON
```
{
"coframe_experiment_id": "zzz...99",
"coframe_experiment_slug": "global-holdout",
"coframe_variant_id": "zzz...98",
"coframe_variant_slug": "global-holdout",
"coframe_user_token": "usr_g7h8i9",
"coframe_impression_id": "imp_d1e2f3",
"coframe_in_global_holdout": true,
"coframe_in_exp_baseline": false
}
```
Key observations: Visitors 1 and 2 share the same `coframe_experiment_id` (`abc...01`), which groups baseline and treatment together for analysis. Visitor 3 has a different `coframe_experiment_id` (`zzz...99`) because they were excluded before reaching any experiment.
## Dashboard vs. Callback IDs
In the Coframe dashboard, baseline and treatment variants appear side by side in the same experiment report. They are unified by their shared `coframe_experiment_id`. This is why the dashboard can show baseline conversion rates alongside each treatment variant — they all belong to the same experiment container.
The key distinction is that baseline and treatment are assigned at different steps in the gate logic (Step 2 vs. Step 3), but both are captured under the same `coframe_experiment_id`. When connecting Coframe data to an external analytics tool, always use `coframe_experiment_id` from the callback as the primary grouping key. Do not attempt to reconstruct experiment membership from `coframe_variant_id` alone, because that field identifies the specific variant, not the experiment as a whole.
## Using Callback Data in Analytics
When forwarding `treatmentsDataV2` data to an external analytics platform:
- Group visitors by `coframe_experiment_id` to assemble the full experiment population (baseline + all treatment variants).
- Identify which variant each visitor saw using `coframe_variant_id` and `coframe_variant_slug`.
- Separate the baseline group from treatment groups using `coframe_in_exp_baseline: true`.
- Identify visitors excluded from all experiments using `coframe_in_global_holdout: true`; they can be used for a clean holdout comparison.
- Use `coframe_user_token` for visitor-level attribution and cross-session stitching.
- Use `coframe_impression_id` for impression-level deduplication when the same visitor sees the same experiment on multiple page views.
## Summary
Concept |
Key point |
3-step assignment |
Global holdout → experiment holdout → variant selection. Each step is a gate; visitors who don’t pass are not assigned downstream. |
`coframe_experiment_id` |
Shared by all visitors in the same experiment, including baseline. Use it to group populations for analysis. |
Multiple experiments per frame |
Targeting is audience-based; only the first matching audience’s experiment activates per visitor per frame. |
Deterministic assignment |
A visitor’s seat is determined by a hash of their `coframe_user_token`, so the same visitor always lands in the same bucket within an iteration. |