The Optimize JavaScript API allows you to use your own JavaScript code to make changes to variants using callback functions. You can also choose to combine these code changes with edits made using the Optimize visual editor. This can be especially helpful when you need to develop code that performs additional actions exactly at the time that changes are applied. It can also be very useful for debugging in general.
In this article:Registering implementation callbacks
Using the Optimize client API, you may provide your own experiment implementation based on JavaScript code that is provided by your own site, and skip using the Optimize editor altogether. Keep in mind though that you will need to implement your own flicker prevention mechanism, since your JavaScript may be called after the page has become visible to a visitor.
An important factor that you need to have in mind is that Optimize is loaded asynchronously. So it is not possible to check for the inclusion of an experiment from synchronous code. Even if things seem to work sometimes, the order of execution is not guaranteed.
Instead you need to provide your implementation as a callback:
// Should be used in scripts placed after the Optimize or GTM // installation or the initialization of the dataLayer variable. // Not required if the Google Analytics gtag implementation is used. function gtag() {dataLayer.push(arguments)} function implementExperimentA(value) { if (value == '0') { // Provide code for visitors in the original. } else if (value == '1') { // Provide code for visitors in first variant. } else if (value == '2') { // Provide code for visitors in section variant. } ... } gtag('event', 'optimize.callback', { name: '<experiment_id_A>', callback: implementExperimentA });
You can register an implementation callback for an experiment with a specific identifier (that you can find in the experiment details page).
Another important factor that you need to have in mind, is that your callback may be invoked before the full DOM tree has become available. So if you need to modify elements on your page, you may need to check for the state of the page and register your implementation to run later, as an event handler of the DOMContentLoaded event (or use something like jQuery.ready if you have jQuery in your page). For example:
function implementExperimentA(value) { ... else if (value == '2') { // Assuming that you have jQuery in your page $( document ).ready(function() { $('#my_element').text('Hello from variant 2'); }) } ... }
MVT experiments
For MVT experiments the value that will be provided from your experiment will include the indexes for all section variants separated by dash ("-").
function implementExperimentA(value) { var sections = value.split("-"); if (sections[0] == '0') { // Provide code for first section for visitors in the original. } else if (sections[0] == '1') { // Provide code for first section for visitors in first variant. } ... if (sections[1] == '0') { // Provide code for second section for visitors in the original. } else if (sections[1] == '1') { // Provide code for second section for visitors in first variant. } }
Handling multiple experiments
You may also provide a single callback that implements multiple experiments:
function implementManyExperiments(value, name) { if (name == '<experiment_id_A>') { // Provide implementation for experiment A if (value == '0') { // Provide code for visitors in the original. } else if (value == '1') { // Provide code for visitors in first variant. ... } else if (name == '<experiment_id_B>') { // Provide implementation for experiment B ... } gtag('event', 'optimize.callback', { callback: implementManyExperiments });
Debugging resources
The Optimize JavaScript API also provides you access to the google_optimize
global variable which has a ‘get’ method and can be useful in debugging scenarios. For example, you can confirm if an experiment is active on a particular page visit even if:
- Changes are non-visible, e.g. they're applied in currently hidden content.
- There are issues, e.g. the targeted elements no longer exist on the page.
- There are no changes in some of the variants.
- The visitor has been assigned to the original variant.
To debug an experiment start by getting the experiment ID from the Optimize experiment details page. Then check which variant is assigned to that experiment by entering the following in Chrome DevTools > Console:
console.log(google_optimize && google_optimize.get('<experiment_id_A>'));
google_optimize
may be undefined if there are no active experiments.If the DevTools console result is undefined, the experiment is not active on the page. Possible reasons include:
- Targeting rules don't match.
- Visitor was not in allocated variant weighting.
- Visitor was excluded because of browser conditions (e.g.: they opted-out of Google Analytics site activity).
- If the page-hiding snippet is installed: Visitor network speed is slower than the async-hide timeout. Learn more.
- The Visual editor is open in another browser tab. Experiments will not run when in edit mode. Learn more.
- You are previewing the variant of another experiment. Only one experiment can run in Preview mode.
- The experiment is using custom activation and hasn’t been activated yet.
- The experiment is using custom activation and was activated but later deactivated (targeting rules no longer match).
If the DevTools console result is:
- 0 – The experiment was run, is active, and the visitor was assigned to variant 0 (original).
- 1 – The experiment was run, is active, and the visitor was assigned to variant 1.
- 2 – The experiment was run, is active, and the visitor was assigned to variant 2.
- and so on
You see all currently active experiments by typing the following into DevTools > Console:
gtag('event', 'optimize.callback', { callback: (value, name) => console.log( 'Experiment with ID: ' + name + ' is on variant: ' + value) });
Delayed page initialization
In some cases you may need to delay critical functionality that takes place during initialization of your page, because you want to use an experiment value. For example you may have Ajax requests that load additional data, and you may want to use different URL paths for those requests for different variants.
For cases like that, it is recommended to utilize the page hiding snippet to ensure that this critical functionality will have a maximum allowed delay (based on the page hiding timeout) and that it will be called in any circumstance. Furthermore you will need to ensure that this functionality will be initialized for users that don't participate in your experiment (either don't match the targeting rules or you are not including 100% of traffic in your experiment).
In order to do that, you can include the page hiding snippet in your page and override the callback that ends the hiding:
function delayedInitialization() { var value = google_optimize && google_optimize.get('<experiment_id_A>'); if (value == '0') { // Provide code for visitors in the original. } else if (value == '1') { // Provide code for visitors in first variant. ... } else if (value == undefined) { // Provide code for visitors not in the experiment } } var hideEnd = dataLayer.hide.end; if (hideEnd) { dataLayer.hide.end = function() { delayedInitialization(); hideEnd(); } } else { delayedInitialization(); }
<STYLE>
line from the page-hiding snippet. Refer to the Optimize DevSite for more information.As you can see above, there is a global window variable called google_optimize
which may be used to get the experiment values. Keep in mind that while it may be tempting to use this directly in your code, it will be defined only after the Optimize container has been loaded so it can't be used in synchronous scripts.
Obviously the google_optimize
variable may not be defined if the Optimize container script fails to load (for any reason i.e. like network problems). Also note that the code above will return undefined experiment values for experiments with custom activation, since those will be typically activated and provide values, at a later time.
Activation events
If you use activation events for your experiment, you should keep in mind that your callbacks may be called multiple times, every time that a variation is re-activated or deactivated. When a variation is deactivated, the experiment value will be undefined.
function implementExperimentA(value) { if (value == '0') { // Original was activated or re-activated // (may be called multiple times). } else if (value == '1') { // First variant was activated or re-activated // (may be called multiple times). ... } else if (value == undefined) { // Experiment was de-activated // (may be called multiple times). }
As expected, your callback will be called multiple times with the same value (on re-activation) or undefined.
value -> 1 1 1 undefined 1 undefined 1 1 ...
Unregistering callbacks
There may be times when you would like to stop your callbacks from been invoked and unregister them from handling experiment values. This is usually the case, when you have experiments that are using activation events and your page or single-page app is moving to a new state that you don't expect and can't handle the experiment value.
Unregister and clean up your callbacks like this:
gtag('event', 'optimize.callback', { name: '<experiment_id_A>', callback: implementExperimentA, remove: true });
When using multiple callbacks, unregister and clean them up like this:
gtag('event', 'optimize.callback', { callback: implementManyExperiments, remove: true });
Related resources
- Using the Optimize anti–flicker snippet – Optimize DevSite
- Server-side experiments – Optimize DevSite
- AMP experiments – Optimize DevSite