
Actions define how variants modify your site. Actions relate to variants rather than a specific page, and apply to specific [Pages](/docs/web-experiment/pages) to control where they take effect.

Experiment applies variant actions during evaluation. Evaluation runs on the initial page load and any time state pushes to or pops from the session history. On a history state change, the SDK first reverts all applied element change and custom code actions, then reevaluates and reapplies actions for the updated page.

## Element changes

Element changes modify existing elements on your site. Web Experiment applies these changes by editing the inner text of an element or appending style to the element based on the change you make in the visual editor.

The visual editor supports the following element changes:

- *[Display](/docs/web-experiment/set-up-a-web-experiment#display-and-visibility)*: Show or remove the element from the DOM.
- *[Visibility](/docs/web-experiment/set-up-a-web-experiment#display-and-visibility)*: Show or hide the element.
- *[Text](/docs/web-experiment/set-up-a-web-experiment#text)*: Update an element's inner text, color, and size.
- *[Background](/docs/web-experiment/set-up-a-web-experiment#background)*: Update a background image or color.
- *[Move](/docs/web-experiment/set-up-a-web-experiment#move)*: Move the position of an element.

## URL redirect

URL redirects load a new URL when a targeted user lands on a targeted page in your experiment. URL redirects happen on the client, and aren't the same as a server redirect with a `3xx` response. Use URL redirect when your variants are different URLs or pages. For example, use it to test landing pages or different versions of a page built in a CMS. URL redirect works with standard A/B tests and [multi-armed bandits](/docs/feature-experiment/workflow/multi-armed-bandit-experiments).

URL redirects retain any query parameters on the original page URL. For example, you create a variant to redirect users from `https://example.com` to `https://example.com/get-started`. If a user clicks a link `https://example.com?utm_source=facebook`, Web Experiment redirects that user to `https://example.com/get-started?utm_source=facebook`.

### Set up a URL redirect

Set up URL redirect through the [Visual Editor](/docs/web-experiment/set-up-a-web-experiment#the-visual-editor) as a variant action. To add a URL redirect to a treatment variant, follow these steps:

1. [Create a Web Experiment](/docs/web-experiment/set-up-a-web-experiment) and open the Visual Editor.
2. Click the Treatment three-dot menu, select **Edit**, then under **Action** select **URL Redirect**.
3. In the URL Redirect panel, add each URL you want to test as a separate variant and click **Apply**.

### Configuration limits

Visual experimentation and Amplitude's low-code implementation apply the following limits when you use URL redirect:

| Setting | Limit | Reason |
|---|---|---|
| Evaluation mode | `local` | Optimizes test performance and minimizes latency impact to end users. |
| Bucketing unit | `User` | Evaluation mode is `local`. |
| Keys | `deviceID` | Evaluation mode is `local`. |
| Audience | `all users` | URL redirect logic uses local evaluation mode. |
| Deployment | Project API key | Simplifies setup requirements. |

{% callout type="note" %}
It's possible for the URL redirect test to have a [Sample Ratio Mismatch (SRM)](/docs/feature-experiment/troubleshooting/sample-ratio-mismatch). Redirect tests work by loading the redirected page, ideally as fast as possible. The sequence for this is:

Control flow:

- Load control page HTML.
- Browser parses and loads dependencies (including the experiment script).
- Experiment script initializes, evaluates the user, and logs an impression if the user is in control.

Treatment flow:

- Load control page HTML.
- Browser parses and loads dependencies (including the experiment script).
- Experiment script evaluates the user, assigns the user to treatment, and triggers a redirect.
- Load treatment page HTML.
- Browser parses and loads dependencies (including the experiment script again).
- Experiment script initializes and logs the impression for treatment.

Because the treatment flow involves more steps before logging the impression, users who bounce quickly (for example, after clicking an ad by mistake) are more likely to count in control than in treatment. This imbalance can appear as Sample Ratio Mismatch (SRM).

Researchers report similar effects: if treatment slows performance, more users leave before impressions log, which reduces recorded impressions. Faster performance has the opposite effect, producing more recorded users in treatment than in control.

To resolve SRM, follow these guidelines:

- Reduce latency in evaluation. The longer the delay before logging impressions, the more pronounced the SRM effect.
- Use local evaluation where possible. For example, target only on browser properties instead of slower remote attributes like Country. Local evaluation reduces the chance that users drop off before logging. The following configuration example shows local evaluation in Amplitude Experiment.

Further reading:

- [Causes of SRM](https://www.lukasvermeer.nl/srm/docs/causes/)
- [Pitfalls in Metric Interpretation (KDD 2017)](https://exp-platform.com/Documents/2017-08%20KDDMetricInterpretationPitfalls.pdf)
- [Diagnosing SRM in Online Experiments (KDD 2019)](https://exp-platform.com/Documents/2019_KDDFabijanGupchupFuptaOmhoverVermeerDmitriev.pdf)
{% /callout %}

### SEO best practices for redirects

Client-side redirects in experiments can affect how search engines index and rank your pages. Follow these best practices to minimize SEO impact.

#### Use temporary redirects during tests

Web Experiment uses client-side redirects that simulate a `302` temporary redirect through `window.location.replace`. The `302` signal tells search engines to keep the original URL indexed during the experiment. Don't use `noindex` tags on your control page, because `noindex` removes the page from search results.

After you pick a winner, implement a permanent server-side `301` or `308` redirect to consolidate link equity to the winning URL.

#### Add canonical tags on variant pages

Add a canonical tag on your experiment variant page that points back to the original (control) URL. The canonical tag prevents search engines from indexing the variant as a separate page.

```html
<!-- On the variant/redirect destination page -->
<link rel="canonical" href="https://example.com/original-page" />
```

#### Prefer server-side redirects for SEO-critical pages

For pages where SEO is critical, consider using server-side assignment and redirects at the edge (CDN) or server level. Server-side redirects execute before the page renders, which:

- Ensures search engine crawlers interpret redirects reliably.
- Minimizes page flicker for users.
- Provides proper HTTP status codes to crawlers.

If you must use client-side redirects, make sure the redirect logic runs in the `<head>` to reduce layout shift.

#### Keep tests short and avoid cloaking

Don't show different content or URLs to users compared to what Googlebot sees. Search engines consider this practice cloaking and may penalize your site. End experiments promptly and remove test logic after concluding to avoid SEO drift.

#### Ensure goals cover both URLs

Define conversion goals that include both control and variant URLs. Web Experiment [preserves query parameters](#url-redirect) through redirects, so attribution and session continuity stay intact.

#### Use Search Console if issues arise

If a search engine indexes your experiment variant page unexpectedly, use [Google Search Console's URL removal tool](https://search.google.com/search-console/removals) as a temporary fix while you address the underlying issue.

## Custom code

{% callout type="note" %}
Custom code requires a Growth or Enterprise plan.
{% /callout %}

Web Experiment applies custom code actions as an optional part of the element changes action. Use the custom code action to write JavaScript, CSS, and HTML that adds elements or customizes your site in ways the visual editor doesn't support.

Apply custom code to specific [Pages](/docs/web-experiment/pages) using the **Apply to** dropdown. The dropdown lets you run different code depending on which Page is active.

{% callout type="tip" %}
You can use custom code together with the [element changes](#element-changes). For example, an engineer can build a custom code component with placeholder text. A non-technical user can then use the visual editor to edit the placeholder text without touching the custom code.
{% /callout %}

Web Experiment applies custom code to your site in the following order:

1. Adds CSS in a `<style>` tag in the page's `<head>`.
2. Parses HTML into a DOM element.
3. Wraps JavaScript in a function, and adds it to a `<script>` tag in the page's `<head>`.
4. Calls the wrapped JavaScript function and passes parsed HTML and utilities as arguments.

### JavaScript

Web Experiment wraps any custom JavaScript in a function, and calls the function when the variant action applies. The function has two parameters you can use with your custom JavaScript code.

- `html`: The custom HTML code parsed as a DOM element object.
- `utils`: An object that contains utility functions you can use in your custom code.

#### Utilities

Web Experiment provides the following utilities:

- `waitForElement(selector: string): Promise<Element>`: Returns a promise that resolves when `waitForElement` finds an element matching the selector in the DOM. Uses `MutationObserver` to listen for elements.

- `remove: (()=> void) | undefined`: A function that you can set inside the JavaScript you inject. Web Experiment calls the `remove` function on page change, when Amplitude reevaluates experiments and reapplies variants.

  The `remove` function is useful for cleaning up changes to the page in single page apps, where the page doesn't fully reload. For example, if you inject an HTML element on a specific page, set the `remove` function to remove that element when the page changes.

### HTML

Web Experiment parses custom HTML as a DOM element, and passes the element to the custom JavaScript code for insertion. The custom HTML can use existing CSS styles and classes, or new CSS that you define.

### CSS

Use custom CSS styles to manipulate existing CSS classes and styles, or add new styles for elements you add with custom HTML. Web Experiment adds custom CSS to a `<style>` tag in the page's `<head>` element.

### Examples

{% callout type="tip" %}
Generative AI like ChatGPT or equivalents can create HTML and CSS for simple elements. ChatGPT generated the initial modal and banner examples that follow, and Amplitude then modified them.
{% /callout %}

#### Insert an element

To insert an element onto your page, follow this pattern:

1. Write the HTML and CSS for the element you want to add to the page.
2. Identify the selector of the parent element you want to insert your new element into. The parent is often the `body`.
3. Paste the following JavaScript code, and update `PARENT_SELECTOR` with the parent element selector from step 2.

   ```js
   utils.waitForElement("PARENT_SELECTOR").then(function (e) {
     e.appendChild(html);
     utils.remove = function () {
       html.remove();
     };
   });
   ```

If you want to insert your element into the parent element at a specific position, use `insertBefore()` instead of `appendChild()`.

#### Add a banner

This example adds a discount code banner to the top of the page.

{% code-group %}
```js JS
utils.waitForElement("body").then(function (e) {
  e.insertBefore(html, e.firstChild);
  utils.remove = function () {
    html.remove();
  };
});
```

```css CSS
.announcement-banner {
  background-color: #fafafa;
  color: #333;
  padding: 10px;
  text-align: center;
  font-family: Arial, sans-serif;
  border-bottom: solid #e5e5e5;
  border-bottom-width: 1px;
}

.announcement-banner p {
  margin: 0;
  font-size: 16px;
}
```

```html HTML
<div class="announcement-banner">
  <p>🎉 Big Sale: Get 25% off on all items! Use code <strong>SAVE25</strong></p>
</div>
```
{% /code-group %}

#### Add a modal

This example adds a modal to the page after a 1 second delay.

{% code-group %}
```js JS
var modal = html;
utils.waitForElement("body").then(function (body) {
  // Append the modal element to the body.
  body.appendChild(modal);

  // Get the close button element
  var closeBtn = document.getElementsByClassName("close")[0];

  // When the user clicks on the close button (x), close the modal
  closeBtn.onclick = function () {
    modal.style.display = "none";
  };

  // When the user clicks anywhere outside of the modal, close it
  window.onclick = function (event) {
    if (event.target == modal) {
      modal.style.display = "none";
    }
  };

  // Show the modal after a 1 second delay.
  window.setTimeout(function () {
    modal.style.display = "block";
  }, 1000);

  // Remove the modal on teardown.
  utils.remove = function () {
    modal.remove();
  };
});
```

```css CSS
/* TODO: Style the action button */
.cta-btn {
  color: white;
  background-color: #000;
  border: #000 solid 1px;
}
.cta-btn:hover {
  color: black;
  background-color: #fff;
  border: #000 solid 1px;
}

/*
 * Modal Boilerplate
 */

/* Modal container */
.modal {
  display: none; /* Hidden by default */
  position: fixed;
  z-index: 1; /* Stay on top */
  left: 0;
  top: 0;
  width: 100%; /* Full width */
  height: 100%; /* Full height */
  background-color: rgba(0, 0, 0, 0.5); /* Black background with opacity */
}

/* Modal content box */
.modal-content {
  background-color: #fff;
  margin: 15% auto; /* Center the modal */
  padding: 20px;
  border-radius: 4px;
  border: 1px solid #888;
  width: 40%; /* Width of the modal */
  max-width: 800px;
  position: relative;
}

/* Close button */
.close {
  color: #999;
  float: right;
  font-size: 28px;
  font-weight: bold;
}

.close:hover,
.close:focus {
  color: #000;
  text-decoration: none;
  cursor: pointer;
}

/* Modal header */
.modal-header {
  margin: 0;
  padding: 0 0 15px 0;
  font-size: 24px;
  font-weight: bold;
}

/* Modal body */
.modal-body {
  margin: 20px 0;
  font-size: 16px;
}

/* Call to Action container */
.cta-container {
  text-align: right;
}

/* Call to Action button */
.cta-btn-base {
  padding: 10px 20px;
  font-size: 16px;
  cursor: pointer;
  bottom: 20px;
  right: 20px;
}
```

```html HTML
<div class="modal">
  <div class="modal-content">
    <span class="close">&times;</span>
    <h2 class="modal-header">
      <!-- TODO: Update modal header text -->
      Join the mailing list!
    </h2>
    <p class="modal-body">
      <!-- TODO: Update modal body text -->
      To get updates on new posts to the blog join the exclusive mailing list
      today!
    </p>
    <div class="cta-container">
      <!-- TODO: Update button link -->
      <a href="https://example.com">
        <button class="cta-btn cta-btn-base">
          <!-- TODO: Update CTA button text -->
          Subscribe
        </button>
      </a>
    </div>
  </div>
</div>
```
{% /code-group %}
