---
name: dfc-cro
description: Run A/B tests on your site with window.dfc(). For developers at DFC portfolio companies who have the DFC script tags installed and want to branch their own templates (Liquid, etc.) by experiment variation.
---

# DFC CRO: Running Tests with `window.dfc()`

Your site carries two DFC script tags in the `<head>`: an inline snippet that
defines `window.dfc()` before the page renders, and an async bundle that
handles tracking. You don't need anything else — call `window.dfc()` from
your own theme code.

## The API

```js
const idx = window.dfc('hide-nav'); // returns 0 or 1
const idx3 = window.dfc('plp-sort', 3); // 3-way: 0, 1, or 2
```

- **`0` is always control.** Show the existing experience unchanged.
- The assignment sticks: the same visitor gets the same index on every call
  and every page view (30 days, consent permitting).
- Calling it **activates** the experiment — the visitor is counted in
  results. Only call it where the test actually applies.
- It's synchronous and never throws; if anything is broken it returns `0`.

## Where to put the call

**In the `<head>`, after the DFC inline snippet, before first paint.** The
earlier you branch, the less flicker. The standard pattern is a class on
`<html>` plus CSS — the browser applies it before rendering anything:

```liquid
{%- if template contains 'product' -%}
  <script>
    if (window.dfc && window.dfc('pdp-shipping-msg') === 1) {
      document.documentElement.classList.add('dfc-pdp-shipping-msg-v1');
    }
  </script>
  <style>
    .pdp-shipping-note { display: none; }
    .dfc-pdp-shipping-msg-v1 .pdp-shipping-note { display: block; }
  </style>
{%- endif -%}
```

For content differences, render both variants in Liquid and let the class
pick one — Liquid runs on the server and can't see the bucket, so branch in
CSS/JS, not in `{% if %}`:

```liquid
<div class="hero" data-dfc-variant="0">{{ section.settings.heading }}</div>
<div class="hero" data-dfc-variant="1" hidden>{{ section.settings.heading_test }}</div>

<script>
  if (window.dfc && window.dfc('homepage-hero') === 1) {
    document.querySelector('.hero[data-dfc-variant="0"]').hidden = true;
    document.querySelector('.hero[data-dfc-variant="1"]').hidden = false;
  }
</script>
```

For changes to elements that render later (theme apps, carousels), bucket in
the `<head>` anyway and apply the change when the DOM is ready — bucketing
early keeps the assignment stable even if the element check fails:

```js
const idx = window.dfc('cart-upsell');
document.addEventListener('DOMContentLoaded', () => {
  if (idx !== 1) return;
  const upsell = document.querySelector('.cart-upsell');
  if (upsell) upsell.remove();
});
```

## Best practices

1. **Gate by page, not just by element.** Wrap the call in your template
   conditional (`{% if template contains 'product' %}`). Activating on pages
   where the change can't show dilutes your results.
2. **One call site per experiment.** Repeat calls return the same index, but
   a single call in the `<head>` is easier to QA and remove.
3. **Name experiments with a short kebab-case slug** — `hide-nav`,
   `pdp-shipping-msg` — and agree the name with your DFC contact before
   launch; the name is the reporting key.
4. **Never reuse an experiment name.** Old assignments persist in visitors'
   browsers for 30 days.
5. **To end a test**, remove the `window.dfc()` call and hard-code the
   winner. There's no traffic ramp — a live test splits evenly (1/N).

## QA

- `?dfc-exp-{name}={n}` in the URL forces you into variation `n`, e.g.
  `?dfc-exp-hide-nav=1`
- `?dfc-reset` clears all DFC assignments from your browser
- `dfc.experiments` in the console lists `[name, index]` pairs activated on
  the current page

## Consent

Assignments and tracking are consent-gated. Without `functional` consent the
assignment doesn't persist and visitors re-randomize each page load. Your
consent platform (OneTrust, Cookiebot, or a `window.__dfc_consent` default)
is wired up as part of the DFC install — if results look off, check consent
rates first.
