1. Overview
  2. Create
  3. Merchandising experiments (A/B)

Merchandising experiments (A/B)

Staged merchandising experiments run split tests on the server. Visitors never load third-party A/B snippets; the edge serves fully formed HTML for one variant per request.

When to use

  • Test hero copy, product grids, or promotional sections on a published page.
  • Compare two block subtrees without hurting Core Web Vitals or layout stability.
  • Keep assignment and caching on the platform (sticky visitor, cacheable per variant).

Scope (v1)

  • Pages only — visual CMS rows in the pages collection, not posts or block-builder presets.
  • At most two active experiments per page.
  • No nested experiments — you cannot put an experiment slice inside an arm of another experiment.

Explicit non-goals

These boundaries apply to merchandising experiments (experiment_slice) as shipped today. They are intentional product limits — not missing features waiting on the next sprint.

v1 deferrals (merchandising may add later)

Not in v1What we ship instead
Desk charts or winner dashboardsDeduped impression counts via API; no conversion UI
More than two active experiments per pageUp to four arms per slice (weights must sum to 100%)
Experiments on posts, product PDPs, or block-builder presetsPublished pages in the canvas editor only

Live in-editor AEO scoring (v1 deferral)

The canvas does not score arms in real time (no “Eonix Score”, traffic-light grades, or side-by-side ranking while you edit).

Not in v1Why
Live AEO panel on the experiment inspectorScoring pipelines and billing AEO lanes are separate from canvas save/publish; in-editor scores would imply certified results before publish
Auto-suggest copy changes from score deltasOut of scope for experiment_slice; belongs in editor/AI products with their own review flow
Per-arm score badges on the structure treeWould require synchronous scoring on every arm edit (latency + cost)

What we do instead: after save, call GET /dxp/api/experiments/:id/aeo-export (staff session). The response includes every arm — headings, paragraphs, list row counts, plain text, token estimate — for offline comparison in your own tooling. Use Preview A / B for visual SSR checks; use export for structured copy review.

A future in-editor AEO experience would be a dedicated product slice (lane metering, score provenance, UI in inspector) — not a silent upgrade to the export JSON shape.

Block-library experiment templates (v1 deferral)

Block library and Block builder store reusable subtrees (presets, forms, layout patterns). They are not wired to CreateExperimentSliceOp or live assignment.

Not in v1Why
“Save as experiment” from block builderExperiments are bound to a published page route, manifest, and edge cache fingerprint — not library entry IDs
Platform-wide “50/50 hero” library SKU that auto-runs A/BWould blur library (copy-on-insert) with runtime merchandising (sticky eonix_vid, impressions)
Pasting a library entry that embeds experiment_sliceNormalization may reject nested experiments; arms must be edited on the page under test

What we do instead: open the pages canvas, focus an eligible container (section, hero, layout_group, card, main), click A/B, edit arms, Save, then Publish. You may still paste library blocks inside an arm after the slice exists.

Client-side A/B libraries (rejected)

Eonix merchandising is SSR-only. The storefront must not load experiment runners in the browser.

RejectedWhy
Google Optimize / GA4 experiment tags, VWO, Optimizely, AB Tasty, etc.Third-party JS hurts CWV, privacy, and CSP; causes layout shift when variants swap in the DOM
“Hybrid” tests (server shell + client swap)Breaks edge cache keys (exp:… variants) and duplicates assignment logic
localStorage / sessionStorage arm picks in theme or GTMNot sticky with eonix_vid; bypasses manifest + deduped impressions

What we do instead: one arm is chosen on the server (ResolveExperiments), HTML is cached per variant on the edge, and the response contains no experiment_slice wrapper — only the winning subtree. Staff preview uses force_arm on SSR preview URLs, not client toggles.

Do not add experiment snippets to themes, header blocks, or site-wide custom HTML expecting the platform to coordinate with them.

Conversion analytics and auto-winner (rejected)

v1 records impressions (which arm HTML was served), not outcomes (orders, sign-ups, clicks, revenue).

RejectedWhy
Purchase / revenue attribution per armNo order→experiment join in the experiment pipeline; commerce events are a separate domain
Funnel goals, click maps, statistical significanceNo desk analytics product on top of experiment_impressions
Auto-promote winning arm to published content_blocksPublishing stays a deliberate human action (Save → Publish); experiments never mutate live BSON without an operator

What we do instead: GET …/impressions/count for smoke tests; AEO export for offline copy comparison. To pick a winner, an editor unwraps the slice (keeps control arm) or copies the winning arm manually, then publishes.

A future conversion product would need its own event schema, privacy review, and likely a different storage path — not an extension of impression dedup.

Multi-page experiments (rejected)

Each page route owns its own experiment manifest and edge cache keys. There is no site-wide or funnel-level experiment ID.

RejectedWhy
One experiment spanning home + PLP + checkoutManifests are per (site, locale, slug); exp:… variant keys are per rendered page
“Sticky arm B” across routes for the same visitorAssignment is crc32(eonix_vid + experiment_id) per slice on that page — different pages may use different experiment_ids with no linkage
Cross-page winner or unified reportingImpressions are recorded per experiment on the page that served HTML; no funnel rollup in v1

What we do instead: run independent slices on each page you care about (respecting the two active experiments per page cap). Accept that a visitor may see arm A on /home and arm B on /sale — by design.

A multi-page product would need shared experiment entities, cross-route manifests, and cache invalidation rules — a new schema, not a wider experiment_slice block.

Other future product boundaries (out of scope unless a new spec says otherwise)

Do not plan integrations or sales promises around these without an explicit merchandising product brief:

  • Checkout, cart, and transactional surfaces — not part of the page canvas experiment model.
  • Member-tier or signed-in personalization — use <eonix-slot> / member cache variants, not experiment_slice.
  • Catalog merchandising — featured sort, per-store overrides, and assortment rules live in catalog/commerce data, not visual page A/B.
  • Cross-site or cross-core experiment sync — experiments are site-scoped on one core DB.
  • Adaptive / bandit allocation — weights are fixed until an operator changes them; no ML traffic rebalancing.
  • Nested or chained experiments — one experiment_slice per eligible container; no experiment inside an arm.
  • Email, journeys, or off-site channels — experiments affect storefront HTML for a page route, not campaign sends.

If you need behaviour outside this list, treat it as a new product (separate spec and schema), not an extension of experiment slices.

Create an experiment

  1. Open the canvas editor for a page (Pages and editor).
  2. Focus an eligible container: section, hero, layout_group, card, or main.
  3. On the block toolbar, click A/B (creates a 50/50 experiment slice around that block).
  4. The page gains an experiment slice with Variant A and Variant B. Edit each arm’s nested blocks like any other content.
  5. Save the page, then Publish when ready — experiments go live with the published snapshot, same as other canvas changes.

Eligible block types get the A/B action automatically when the block is focused; experiment slices themselves do not offer A/B again.

Structure tree and inspector

  • Structure (left aside) lists the experiment as Experiment · A 50% · B 50% (or · paused when paused), with Variant A / Variant B nested underneath. Experiment rows are not draggable — reorder the underlying blocks inside each arm on the canvas.
  • Inspector (right aside): select the experiment slice (or click Settings on its toolbar) for status, traffic weights, SSR preview per arm, Edit arm focus, and Unwrap.

Experiment toolbar

When the experiment slice is focused, the chrome shows:

ControlEffect
SettingsOpens the right-hand Merchandising experiment inspector.
PauseAll visitors see Variant A (control) until you Resume.
ResumeRestores weighted split (active status).
UnwrapRemoves the experiment and keeps Variant A only (destructive; confirms first).
Preview A / Preview BOpens SSR preview for that arm (force_arm query; does not change live assignment).
DeleteRemoves the entire experiment slice.

Traffic weights

While status is active, set each variant’s percentage under Traffic, then Save weights. Weights must sum to 100% (the editor normalises on save). Default for a new slice is 50/50.

SSR preview (staff)

Use SSR preview on the canvas toolbar to see published HTML in a dialog. For a specific arm:

  • Click Preview A or Preview B on the experiment chrome, or
  • Append force_arm=<experiment_id>:<arm_key> to the preview URL (comma-separated for multiple experiments), e.g. force_arm=abc123:b for Variant B.

Preview does not change the visitor’s eonix_vid cookie or live assignment.

Pause and control

  • Paused experiments always serve Variant A on the storefront.
  • Active experiments assign arms by configured weights (see below).

AEO export (offline scoring)

GET /dxp/api/experiments/:experiment_id/aeo-export?page_id=… (or slug=…) returns structured JSON for every arm — headings, paragraphs, list row counts, plain text, and a rough token estimate. Use this to compare copy for answer-engine optimisation offline; there is no live “Eonix Score” in the editor yet.

Requires a signed-in canvas admin session.

Impressions

The edge records one impression per visitor per experiment per day (deduped server-side). For smoke tests:

GET /dxp/api/experiments/:experiment_id/impressions/count

There is no desk dashboard chart in v1.

How assignment works

  1. First visit sets a sticky eonix_vid cookie (HttpOnly).
  2. On publish, the platform writes a page experiment manifest the edge reads for active experiments and weights.
  3. Each active experiment picks an arm from crc32(visitor_id + experiment_id) against those weights.
  4. The edge cache key includes an exp:… variant so HTML stays cacheable per arm combination.
  5. Public HTML never contains the experiment_slice wrapper — only the chosen arm’s blocks are rendered.

Desk APIs (integrators)

MethodPathPurpose
POST/dxp/api/create-experiment-sliceWrap a block in a new slice
PATCH/dxp/api/update-experimentStatus, weights, labels
POST/dxp/api/unwrap-experimentKeep control arm only
GET/dxp/api/experiments/:id/aeo-exportStructured copy per arm
GET/dxp/api/experiments/:id/impressions/countDeduped impression count

Canvas saves and publishes also flow through the engine PageCanvas gRPC surface when ES_ENGINE_ADDR is configured.

  • Pages and canvas editor
  • Block builder — presets only; live experiments are on pages
  • Platform developer doc eonixstream/docs/content-blocks-schema.mdexperiment_slice BSON shape