A friendly setup guide for A/B testing

Getting started

This is as technical as it gets. After this, there should be no more scary setup work on your side. We give you the code you need, and we can help along the way at any step if you want us to.

1

Add the FreeCRO code snippet

Once you are accepted as a user on FreeCROTool.com, a tag needs to be implemented on each page of your site. This allows testing changes to happen and lets us track the paths visitors take after they have seen a control or variant.

We can help with the implementation, and the platform provides the code so it can be copied and pasted easily.

Ideally, the tag should be added exactly as supplied and placed as close to the top of the <head> section as possible. If it must be injected asynchronously, we recommend adding a pagehide script too, and we can help with that.

On Shopify, WordPress and many similar platforms, this can usually be done in one place and that is it - all done. For example, that might be a theme file such as Shopify's theme.liquid, a site-wide header include, or a single custom code area in WordPress.

2

Set up Google Analytics once

For Google Analytics reporting to work properly, a small set of custom definitions must be added in Admin → Custom definitions. This must be done early and only once.

If the definitions are missed, FreeCROTool reporting will not be able to pull in the specific testing events that make the reports easier to read.

3

Shopify optional but recommended

If you are on Shopify and want tracking on the checkout or thank you page, we usually need a small extra piece of code there as well, because Shopify checkout testing is limited.

Without that extra piece, confirmation and order value tracking may not be available.

Shopify checkout tracking details

This is only needed for Shopify stores that want confirmation and order value tracking. If you do not need checkout tracking, you can ignore this.

The GA_MEASUREMENT_ID comes from Google Analytics 4. Open your GA4 property, go to AdminData streams, open your web stream, and copy the Measurement ID that starts with G-. The GA_API_SECRET also comes from that same web stream, under Measurement Protocol API secrets, where you create a new secret string.

const GA_MEASUREMENT_ID = "**G-??????????**"; // Your Google Analytics Measurement ID
const GA_API_SECRET = "**A random string of characters**"; // A secret key that we need to make in GA
const DEFAULT_CURRENCY = "GBP";
const ENABLE_GA_DEBUG_MODE = true;

function safeParseJSON(value, fallback) {
    try {
        return JSON.parse(value);
    } catch (e) {
        return fallback;
    }
}

function makeClientId() {
    return String(Math.floor(Math.random() * 1e10)) + "." + String(Math.floor(Date.now() / 1000));
}

async function getOrCreateClientId(browser) {
    const existing = await browser.localStorage.getItem("fcro_ga4_client_id");
    if (existing) return existing;
    const created = makeClientId();
    await browser.localStorage.setItem("fcro_ga4_client_id", created);
    return created;
}

async function getOrCreateSessionId(browser) {
    const existing = await browser.localStorage.getItem("fcro_ga4_session_id");
    if (existing) return existing;
    const created = String(Math.floor(Date.now() / 1000));
    await browser.localStorage.setItem("fcro_ga4_session_id", created);
    return created;
}

async function getActiveAssignments(browser) {
    const raw = await browser.cookie.get("_cro_active_tests");
    if (!raw) return [];
    const parsed = safeParseJSON(decodeURIComponent(raw), []);
    if (!Array.isArray(parsed)) return [];

    return parsed
        .map((x) => ({
            testID: String((x && x.testID) || ""),
            variantNumber: String((x && x.variantNumber) || ""),
            status: String((x && x.status) || "")
        }))
        .filter((x) => x.testID.match(/^[0-9]+$/) && x.variantNumber.match(/^[0-9]+$/));
}

async function wasAlreadySent(browser, transactionId, testID) {
    const key = "fcro_purchase_sent_" + transactionId + "_" + testID;
    const existing = await browser.localStorage.getItem(key);
    if (existing === "1") return true;
    await browser.localStorage.setItem(key, "1");
    return false;
}

async function sendGa4Event(clientId, eventName, params) {
    const url =
        "https://www.google-analytics.com/mp/collect?measurement_id=" +
        encodeURIComponent(GA_MEASUREMENT_ID) +
        "&api_secret=" +
        encodeURIComponent(GA_API_SECRET);

    const payload = JSON.stringify({
        client_id: clientId,
        events: [{ name: eventName, params }]
    });

    if (typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function") {
        try {
            var beaconBody = new Blob([payload], { type: "text/plain;charset=UTF-8" });
            var sent = navigator.sendBeacon(url, beaconBody);
            if (sent) {
                return;
            }
        } catch (e) {
        }
    }

    await fetch(url, {
        method: "POST",
        mode: "no-cors",
        keepalive: true,
        headers: { "Content-Type": "text/plain;charset=UTF-8" },
        body: payload
    });
}

function buildGaEventParams(baseParams, sessionId) {
    const params = Object.assign({}, baseParams || {});
    params.session_id = String(sessionId || "");
    params.engagement_time_msec = Number(params.engagement_time_msec || 1);
    if (ENABLE_GA_DEBUG_MODE) {
        params.debug_mode = 1;
    }
    return params;
}

function subscribeToCheckoutCompleted(deps) {
    var analytics = deps.analytics;
    var browser = deps.browser;

    if (!analytics || typeof analytics.subscribe !== "function") {
        throw new Error("Shopify analytics API is not available in this runtime.");
    }
    if (!browser || !browser.localStorage || !browser.cookie) {
        throw new Error("Shopify browser API is not available in this runtime.");
    }

    analytics.subscribe("checkout_completed", async (event) => {
        const clientId = await getOrCreateClientId(browser);
        const sessionId = await getOrCreateSessionId(browser);
        const assignments = await getActiveAssignments(browser);
        if (!assignments.length) return;

        const checkout = (event && event.data && event.data.checkout) || {};
        const amount =
            Number(
                (checkout.totalPrice && checkout.totalPrice.amount) ||
                (checkout.total_price && checkout.total_price.amount) ||
                0
            ) || 0;

        const currency =
            String(checkout.currencyCode || checkout.currency_code || DEFAULT_CURRENCY).toUpperCase();

        const transactionId = String(
            (checkout.order && (checkout.order.id || checkout.order.name)) ||
            checkout.orderId ||
            checkout.id ||
            ""
        );

        for (const a of assignments) {
            if (transactionId) {
                const dup = await wasAlreadySent(browser, transactionId, a.testID);
                if (dup) continue;
            }

            try {
                await sendGa4Event(clientId, "freecro_purchase", {
                    ...buildGaEventParams({
                        name: "confirmation",
                        value: amount,
                        currency: currency,
                        transaction_id: transactionId,

                        fcro_eID: a.testID,
                        fcro_vNum: a.variantNumber,
                        fcro_vID: a.testID + "v" + a.variantNumber,
                        fcro_status: a.status || ""
                    }, sessionId)
                });
            } catch (e) {
                console.error("freecro_purchase send failed", e);
            }
        }
    });
}

if (typeof register === "function") {
    register(({ analytics, browser }) => {
        subscribeToCheckoutCompleted({ analytics, browser });
    });
} else if (typeof analytics !== "undefined" && typeof browser !== "undefined") {
    subscribeToCheckoutCompleted({ analytics, browser });
} else {
    throw new Error("Shopify pixel APIs were not found. Run this inside a Shopify pixel context.");
}
4

We can help with both

We can help with the tag implementation and the Google Analytics setup. To reduce errors, we recommend copying and pasting the details exactly as provided.

If you need support, we can guide you through the steps or point you to the exact code to copy.

Google Analytics custom definitions

Add these in GA Admin → Custom definitions. Ignore the last changed date column below; the important parts are the parameter name, the friendly label and the event parameter.

Dimension name Friendly name / Description Scope Event parameter
fcro_event_name FreeCRO event name Event name
fcro_event_value FreeCRO event value (for revenue) Event value
fcro_exp_and_variant FreeCRO Experiment(v)VariantNumber Event fcro_vID
fcro_experiment_id FreeCRO Experiment ID Event fcro_eID
fcro_impression_path FreeCRO Page path where experiment was entered Event fcro_path
fcro_variant_number FreeCRO Variant Number Event fcro_vNum
We can help you set these up, and we strongly suggest copying and pasting the values directly to avoid mistakes.

No data will appear in reports without doing the above, so this is quite important, if you need help with this let us know and we'll setup a meeting to help :)

Example code snippet

This is the type of tag the platform provides for copying into your site:

All testing platforms need something on your site so they can run the test. There is no way to make it work without adding the snippet, but we keep that part as simple as possible.

<script src='https://freecrotag.s3.eu-west-1.amazonaws.com/XXXXXXXXXX_tag.js'></script>

A few extra things worth including

  • The tag should be on every page where visitors may travel during or after a test.
  • The correct GA4 property should be selected before reporting is expected to work.
  • Some GA4 data can take time to appear, so first reports may not be immediate.

Need help getting set up?

If you already have access to your site and GA account, you may be very close to going live. We can help make the setup simple and keep the copy/paste steps accurate.

© 2026 FreeCROTool. All rights reserved | Site map | Terms and conditions