Skip to content

JavaScript API

Attach the rl/api library to make Drupal.rl available. It is a thin transport proxy that coalesces every decide, impression, and conversion fired on the page into a single batched POST to rl.php, so N experiments on the same page produce one or two requests instead of one set per experiment.

Methods

Drupal.rl.decide(experimentId, armIds)

Ask for a decision when the variant needs to be chosen client-side. The arm list must be read from the DOM; see the discipline section below.

var container = document.querySelector('[data-rl-experiment="hero_cta"]');
var armIds = container.dataset.rlArms.split(',');
Drupal.rl.decide('hero_cta', armIds).then(function (armId) {
  showVariant(armId);
});

Drupal.rl.rank(experimentId, armIds)

Ask for a full ranking when you need to sort, not just pick a winner. Use case: reordering accordion items, FAQ lists, or any sortable content by visitor engagement.

var items = document.querySelector('[data-rl-experiment="faq_sort"]');
var faqArms = items.dataset.rlArms.split(',');
Drupal.rl.rank('faq_sort', faqArms).then(function (sorted) {
  // sorted = ['t3', 't0', 't1', 't2'], all arms, best first
  sorted.forEach(function (armId) {
    items.appendChild(items.querySelector('[data-rl-arm="' + armId + '"]'));
  });
});

Drupal.rl.turn(experimentId, armId)

Record an impression when the variant becomes visible.

Drupal.rl.turn('hero_cta', 'v0');

Drupal.rl.reward(experimentId, armId)

Record a conversion when the user clicks, submits, or converts.

Drupal.rl.reward('hero_cta', 'v0');

Drupal.rl.flush()

Force an immediate flush of the event queue.

Drupal.rl.flush();

Batching behaviour

Events accumulate for 500 ms and then flush in one POST. Decide, turn, and reward events share the same queue and the same request, so a page with a DXPR Builder variant block plus tracking on other elements ends up making a single round trip. Buffered tracking events are also flushed via navigator.sendBeacon on visibilitychange and pagehide so they survive navigation.

Discipline for decide() and rank()

Never hardcode arm IDs in JS. Always read them from a DOM attribute that the server-side renderer emitted.

The DOM is downstream of the same server-render pipeline that produced the decide's context. When the experiment manager adds or removes a variant, the consumer's page cache is invalidated, the next render emits the new attribute, and JS picks it up. JS never asserts what the arm set is; it just echoes whatever the current cached HTML says. This keeps Drupal.rl.decide() and Drupal.rl.rank() drift-free without requiring the RL core to store arm lists.

The convention your builder uses internally (numeric v0..vN, UUIDs, node IDs, anything matching ^[a-zA-Z0-9_-]+$) is whatever you emit into the attribute. The RL core is arm-agnostic.

If the server returns no decision for an experiment (not registered, no data, network error), the returned promise resolves to armIds[0] so callers never need a .catch() for the common path.

Drupal.rl is one transport among several. Modules that already ship their own tracking JS (like rl_sorting, which batches turns on its own 100 ms window and posts them as form data) keep working untouched.