FabricFabricExperiments
Migration

Migrating from Mojito

Move a Mojito JS Delivery wave to a Fabric Experiments YAML — automatically, with one command.

Fabric Experiments ships a one-shot importer plus runtime support for the features Mojito users rely on most: domOps, divertTo, manualExposure, holdback, and a Trigger DSL.

What's automatic

Run the importer:

fx import-mojito ./waves --out ./experiments

It walks every wave directory and emits one Fabric YAML per wave. 1:1 conversion:

MojitoFabric
idid
namename
sampleRatesampleRate
divertTodivertTo
manualExposuremanualExposure
recipes[<key>].namevariants[<key>].name
recipes[<key>].sampleRatevariants[<key>].weight (× 10000)
recipes[<key>].js (file)variants[<key>].js (inlined)
recipes[<key>].css (file)variants[<key>].css (inlined)

What needs review

trigger.js runs arbitrary code in Mojito. The importer preserves the original code as a YAML comment header and emits trigger: { kind: 'auto' } so the import is unblocked — but you must translate the trigger to one of:

  1. The declarative Trigger DSL:

    trigger:
      kind: urlMatch
      pattern: /cart
    # or
    trigger:
      kind: waitForSelector
      selector: 'button.add-to-cart'
      timeoutMs: 3000
    # or
    trigger:
      kind: event
      name: 'app:ready'
  2. A host-supplied trigger override at SDK init time:

    createClient(manifest, {
      subjectId,
      manifestUrl,
      triggers: {
        'w12-checkout': () => location.pathname.startsWith('/checkout'),
      },
    });

Side-by-side example

Mojito wave (waves/w12-hero/config.yml):

state: live
sampleRate: 0.5
id: w12-hero
name: W12 Hero
recipes:
  "0":
    name: Control
  "1":
    name: Treatment
    js: 1.js
    css: 1.css
trigger: trigger.js

After fx import-mojito waves experiments/w12-hero.yaml:

# Imported from Mojito wave
# Source: waves/w12-hero
# Original state: live
#
# Original trigger.js (NOT auto-applied):
#   function trigger(test) {
#     if (location.pathname === '/') test.activate();
#   }
id: w12-hero
name: W12 Hero
sampleRate: 0.5
variants:
  - key: "0"
    name: Control
  - key: "1"
    name: Treatment
    js: |
      // contents of 1.js
    css: |
      /* contents of 1.css */
trigger:
  kind: auto

Edit the trigger to match the original intent, then:

fx validate experiments
fx plan experiments
fx apply experiments

Recipe → variant authoring tips

Prefer domOps over js for new variants — declarative DOM ops are CSP-friendlier than new Function(variant.js) and survive content security policy hardening:

variants:
  - key: control
    name: Control
  - key: treatment
    name: Treatment
    domOps:
      - op: replaceText
        selector: 'h1.hero'
        value: 'Ship faster'
      - op: setStyle
        selector: 'a.cta'
        name: background
        value: '#16a34a'

The full op list is in Reference / YAML.

Runtime parity table

Mojito featureFabric equivalent
?mojito_<id>=<recipe> URL preview?fxpreview=<expId>:<variantKey>&fxtoken=<jwt> (signed; not exposure-tracked)
Sticky cookiesCookie fx.<expId> + localStorage fallback
manualExposure: true + test.trackExposureEvent()manualExposure: true + client.expose(id)
divertTodivertTo
holdback (per-recipe sampleRate < 1)holdback: 0.1 (compiles to audience.sampleRate = 0.9)
Snowplow tracker@fabricorg/experiments-web-adapterssnowplowAdapter()
GA OptimizegaAdapter({ eventName: 'experiment_exposure' })

What's not portable

  • gaExperimentId (Google Optimize is sunset). Use gaAdapter to send a custom GA4 event instead.
  • Wave-level state: inactive — Fabric uses the lifecycle (draft → review → approved → running → paused → killed). The importer drops state and leaves you in draft; promote with fx apply.
  • The mojito_* URL preview pattern. Use fx preview <expId> --variant <key> to mint signed Fabric preview links.

On this page