All Collections
FAQs & Troubleshooting
How the Mutiny Client Code Works
How the Mutiny Client Code Works

The Mutiny client sits at the center of everything you do with Mutiny. This document will discuss the technical detail of how it works.

Updated over a week ago

The Mutiny Client sits at the heart of the Mutiny ecosystem. It is necessary for both configuring and applying the personalizations you make. This document intends to give a detailed overview of how the Mutiny client works so that you can better understand the systems at play when we personalize content for your users.

What is the client?

The Mutiny Client is a JavaScript bundle that is hosted on Amazon S3 and can be added to your websites through a <script> tag. Any time a visitor comes to the your page, they’ll be loading the Mutiny client. The Mutiny client can do a number of things, including finding different pieces of content on the page to personalize, make network requests to our servers, and talk to other JavaScript APIs that may be on your website. The Mutiny client is what gives us the ability to change your page on the fly for every visitor.

The client code snippet is short. It consists of two script tags, and looks like this:

The first script tag is what we call a shim. Its job is to make the API we expose in the client available to the page before the script has loaded and the client can respond to calls. The reason it is just a raw piece of minified JavaScript (rather than what we see in the second script tag) is so that it is guaranteed to execute. If a visitor has an ad blocker that blocks our CDN, the Mutiny API will still be accessible (though functionally inert) and any customer code using that API will not encounter errors.

The second <script> tag points to a file on our Content Delivery Network. This file is the Mutiny client.


When a visitor loads a page with the Mutiny client installed, their browser will begin going through the HTML of the page, loading scripts, and rendering elements. When it reaches the Mutiny snippet, a network request will go out to our CDN requesting your specific client. On completion of that request, the client will begin executing before the browser moves on parsing past our script. This brings us to the Mutiny Client’s initialization phase.

The client ultimately wants to do a few things:

  1. Find out who this visitor is.

  2. Decide what it should show this visitor (if anything).

  3. Report back to the Mutiny server.

  4. Watch for any conversions to take place.

The hider (preventing flicker)

While doing loading personalizations, we want to ensure that your site is able to load as quickly as possible, while also preventing the visitor from realizing the page has been changed/personalized.

Both of these requirements lead us to something we call “the hider”. While the client is executing, the browser isn’t adding new things to the document, it’s not requesting the font files or images, it’s just executing Mutiny code. If we were to do everything synchronously, adding our client to your page would make your page feel noticeably slower to load. This would be bad, so instead, we want to do some things asynchronously and allow the browser to continue loading the page while we’re waiting on some things (we’ll get to that in a moment).

On the other hand, If we just let the browser carry on with it’s business, the page will load as if Mutiny wasn’t there. What would likely happen in this case is the original content would be loaded on the page before Mutiny has a chance to change it. Once Mutiny fully loads, we’d then change the original content. The visible changing of content from control to personalized or vice versa is what we call a ‘flicker’, and is notably a bad experience for a visitor.

In order to prevent this flicker the client attaches some CSS to the page before letting the browser carry on with loading the page. This CSS sets the opacity of any element you may have personalized to zero, hiding it (hence the name) from view until further notice. The hider will be removed by the client once personalizations have been applied (or the visitor is in the control group), or after a timeout of three seconds in the event that one of the resources the client depends on is slow to respond or blocked.

💡 By setting the element’s style to opacity: 0, we avoid any content shifts that may occur if we otherwise just removed the element that was being personalized. The element still exists, it just isn’t visible.

Asynchronous requests

Once the hider has been applied to your page, our initialization process can confidently request other resources while the browser continues loading the page. The client makes three asynchronous requests:

  1. A request for user-data

  2. A request for client-data

  3. A request to load the next JS bundle required for applying personalizations

User Data

The request for user-data is sent to our server with a unique token to identify the user and a unique token to identify the session they’re in. The server will take these pieces of information and get together a payload of data it knows about this particular visitor. Ultimately this data is used to determine what segments the visitor qualifies for and can be used to fill in variables you may have used in your personalizations.

Client Data

The request for client-data also goes to our server using the company token in the URL so the server knows what company’s data it should return. This data is a collection of all the segmentation details, experience details, personalizations, conversion configuration details, and anything else necessary to deliver personalized experiences to visitors that can be configured in the app. Most of the time this request will hit a Fastly cache as this data need only be re-calculated anytime the you create, updates or delete something that would be delivered in this payload (a personalization or segment for example).

JavaScript bundle

The client uses webpack to split the code into bundles which can be loaded lazily in the browser. We want the initial client file loaded in the visitor’s browser to be as small as possible as the page will be blocked from loading until it is requested and has finished executing. So at this point, the client is preparing to begin the next phase of execution where it will try to say what the visitor should see and then render that for them. This requires a chunk of the client that is split into its own bundle and so the request for that code is made here.

Cookie Consent

When our asynchronous requests are made the client has stopped blocking the browser from continuing its loading of the page. Any elements that may be personalized later are conveniently hidden. When those asynchronous requests have all completed, the client has everything it needs to categorize the visitor, decide what they should see, and show it to them.

Before we do that though, there’s a few things to take care of first. Remember the shim from earlier? We’re jumping back there because the shim stubs some important functions for the client and we’ll need to start by checking for any invocations of those functions before we do anything else.

As you browse the web you’ve probably run into cookie consent banners before. There are legal regulations (GDPR, CCPA) in place that require websites to respect the privacy decisions of visitors and these banners are the de facto way to abide by those regulations. Mutiny’s client must similarly abide by those regulations and offer you a way to disable tracking in Mutiny if the visitor has opted out of cookies.

Our shim exposes three cookie related functions optIn, optOut and defaultOptOut which we start by flushing in the event they were called. If you have configured your site to opt visitors out of being tracked by default, you'll call defaultOptOut before our client has loaded. optIn and optOut tend to be wired up to controls the visitor may interact with (for example an ‘Accept all’ button on a cookie consent banner). The client must immediately determine if these consent functions have been used so that the client can remain compliant as it proceeds with the next few steps. More information on cookie consent can be found here.

Evaluating Experiences

Now that we have everything in place, its time to evaluate which experiences should be shown to users.

In the Mutiny app, you can configure segments and experiences. Segments are fairly straightforward definitions of what user data configuration indicates a particular audience. Experiences are a little more complicated. Experiences can be in various states (though the client will only be aware of live experiences): pending, launched, promoted, archived. They can run on different URLs (or even on multiple URLs). They can have different types altogether: content personalizations (I change the text of my H1), components (banner, sidepop), redirect. All of these characteristics play a part in deciding what we’re going to show our visitor.

At a high level, we evaluate by going through each of these steps.

  1. Check which audience definitions this visitor matches.

  2. For those audiences, check which experiences are running on the URL our visitor is viewing.

  3. Given those experiences, put them in order of priority (as configured by you during the Launch step of experience creation).

  4. Bucket those experiences by type.

  5. Select the experience(s) to render.

Segment evaluation

This is perhaps the easiest piece. Any visitor can qualify for more than a single segment. Whether they qualify for a segment is determined by checking if their user data fits whatever rules were setup by your segment definition.

If we have a segment that says the visitor must have user data for a Hubspot contact with a company name including HQ and a segment that requires our visitor be viewing from a mobile device, any Mutiny employee visiting the site on their phone would likely qualify for both.

URL evaluation

Experiences can be configured to run on a single URL (like or on multiple URLs defined by a set of rules (like any page with blog/ in the path). If an experience is configured on a URL with query parameters (like the query parameters of the visited page must include and match the query parameters configured (in this case job=engineer) but can include additional unrelated query parameters (like any utm_term) and still qualify.


In the Mutiny app, if you launch an experience on a page where another experience is running, you are prompted with a modal asking you to order your experiences by priority. Since visitors can qualify for multiple segments, if my All Traffic segment has an experience running on the and my Startups segment also has an experience on the client needs to know which of those experiences should be shown. More than one experience can be shown on any given page but for these cases where a choice has to be made the experiences are ordered based on how the prioritization was configured within the app. More information about experience prioritization can be found here.


Experiences are bucketed into broad categories: ABM campaigns, redirect experiences, component experiences, exit intent experiences, concurrent experiences, and on-page experiences.

Redirect, component, exit intent, and on-page experiences are determined by the type they’re configured as in the Mutiny app.

Concurrent experiences are ones that have been marked to be run concurrently in the prioritization modal (i.e. let this experience run no matter what else might be running on the page).

ABM campaigns are experiences configured in the Outbound section of our app.

Experience selection

With our different buckets of experiences we decide what experience(s) will be applied. The selection rules are as follows:

  1. If the visitor qualifies for any ABM campaigns, show them the highest priority campaign & all concurrently running experiences.

  2. If the visitor qualifies for any redirect experiences, execute the redirect configured by the highest priority redirect experience.

  3. Otherwise, apply the highest priority on-page, the highest priority questionnaire, and the highest priority exit intent experience (if they exist) as well as any concurrently running experiences.

This means a visitor can be viewing a personalized headline, Mutiny banner, and Mutiny exit intent modal all at the same time.

Rendering Experiences

The final step here is to render the experience. When rendering an experience the first thing to determine is whether the visitor should see that experience at all. When experiences are first launched they have a 50% hold out group which are shown the page without the experience’s personalizations applied. This is considered an ‘experiment’. When those experiences are promoted, that hold out is reduced to 0%, meaning all qualifying visitors should see the experience.

The actual logic for determining whether the visitor sees control or personalized lives in the client. The act of determining whether a visitor is meant to see control or personalized is as simple as generating a random number from zero to one and comparing it to the holdout group cutoff.

Don't be a stranger

If you have any questions, we’re here to help! Please feel free to contact us at any time, either through intercom chat or via

Did this answer your question?