Run A/B Tests on Squarespace with Optimizely Web Experimentation

David Sertillange, Independent experimentation specialistDavid SertillangeIndependent experimentation specialist
·5 min read

Squarespace gives you exactly one place to put a script that runs before the page paints, and exactly one way to observe a conversion without touching a template. Both are usable, and the whole of an Optimizely Web Experimentation integration on Squarespace is knowing which is which — because the alternatives that look equivalent in the editor are not equivalent at all.

This guide covers installing the Web Experimentation snippet in Squarespace's code injection, forwarding Squarespace form submissions and commerce events as Optimizely custom events, and writing variations that survive a template change. Size the test on the sample size calculator before you build it — a Squarespace site is usually a low-traffic site, and the honest answer is often "this test needs more weeks than you have".

How Web Experimentation Runs on Squarespace

Squarespace serves its own HTML with its own runtime for forms, galleries and commerce. The Optimizely snippet goes into site-wide header code injection, runs synchronously before paint, and applies DOM changes. Conversions are observed by listening for the page states Squarespace produces after a form or a checkout.

flowchart TD
    A[Visitor loads a Squarespace page] --> B[Header code injection: Optimizely snippet]
    B --> C[Variation applied before paint]
    C --> D[Visitor submits a form or completes an order]
    D --> E[Squarespace shows its confirmation state]
    E --> F{Form name in the allowlist?}
    F -->|yes| G[Push onto the Optimizely queue]
    F -->|no| H[Dropped, silently]
    G --> I[Custom event recorded against the variation]

Which Injection Point, and Why It Matters

Squarespace offers several. Only one is correct for this:

  • Settings → Advanced → Code Injection → Header. Site-wide, in <head>, before content. This is where the snippet goes.

  • Code Injection → Footer. Runs after the page has painted. Guaranteed flicker.

  • Page settings → Advanced → Page Header Code Injection. One page only, after the site-wide header. Useful for page-specific tracking, never for the snippet.

  • A code block in the page content. Renders inside <body>, far too late, and Squarespace may sanitise it.

Code injection requires a Business plan or above. On Personal there is no supported way to install the snippet, and no code block arrangement changes that.

Prerequisites

  • A Squarespace Business plan or higher, which is what unlocks code injection.

  • The Optimizely Web Experimentation snippet URL for your project.

  • Named forms. Every Squarespace form block has a name; those names are your allowlist keys. Agree on them before writing code, because renaming a form in the editor silently breaks the mapping.

  • Custom events created in Optimizely for each conversion, with API names noted. An event pushed under a name that does not exist in Optimizely is discarded without an error.

  • Event properties created in Optimizely for anything you want to segment by.

Step 1: Add the Snippet to Header Code Injection

Paste the snippet as the first thing in Settings → Advanced → Code Injection → Header, then save.

<script src="https://cdn.optimizely.com/js/YOUR_PROJECT_ID.js"></script>

Nothing else belongs above it — not a tag manager, not a consent banner loader, not a font preload. The snippet has to win the race against the first paint, and every script above it in that box runs first.

Squarespace's own scripts load after code injection, which is what you want: the variation is applied before the site runtime initialises.

Step 2: Forward Form Submissions as Custom Events

Squarespace form blocks post through the platform's own handler and then swap the block for a confirmation message. There is no documented submit event, so the reliable signal is the confirmation state appearing.

Put this in header code injection, below the snippet:

<script>
  window['optimizely'] = window['optimizely'] || []

  var FORM_EVENTS = {
    'Newsletter Signup': 'newsletter_signup',
    'Contact Us': 'contact_request',
    'Book a Consultation': 'consultation_request',
  }

  document.addEventListener('DOMContentLoaded', function () {
    document.querySelectorAll('.form-wrapper').forEach(function (wrapper) {
      var title = (wrapper.querySelector('.form-block-title') || {}).textContent
      var eventName = FORM_EVENTS[(title || '').trim()]
      if (!eventName) return

      new MutationObserver(function (mutations, observer) {
        if (!wrapper.querySelector('.form-submission-text')) return
        window['optimizely'].push({
          type: 'event',
          eventName: eventName,
          tags: { form_title: (title || '').trim() },
        })
        observer.disconnect()
      }).observe(wrapper, { childList: true, subtree: true })
    })
  })
</script>

Three properties of that code are deliberate. The allowlist means an unmapped form sends nothing rather than a junk event. The observer disconnects after firing, so a re-render cannot double-count a conversion. And the event name is looked up, never derived from the title — event names in Optimizely are fixed strings, and deriving them from editable copy means the day someone rewrites a heading is the day the metric goes to zero.

Step 3: Count Commerce Orders

On a Squarespace commerce site the order confirmation page is the conversion. It is a real navigation, so a path check is enough:

<script>
  if (window.location.pathname.indexOf('/checkout/order-confirmed') === 0) {
    window['optimizely'].push({
      type: 'event',
      eventName: 'order_completed',
      tags: { revenue: Math.round(window.Static.SQUARESPACE_CONTEXT.orderTotal * 100) },
    })
  }
</script>

revenue must be an integer number of cents. Read the order total from whatever Squarespace exposes on your template rather than parsing it out of the rendered page — a price string with a currency symbol and a thousands separator parses differently in different locales, and the failure is a revenue metric that is quietly wrong rather than absent.

If the confirmation page is not reliably distinguishable on your template, fall back to the order confirmation email trigger in your own systems rather than guessing from the DOM.

Step 4: Write Variations That Survive a Template Change

Squarespace class names are template implementation details. .sqs-block-content and its neighbours change when a template is updated and when a section is re-added in the editor, and a variation keyed to one of them stops applying without any error.

  • Prefer content-based selection. Optimizely's visual editor will offer a fragile deep selector; take the time to key on a heading's text, a block's data-block-type, or an anchor's href instead.

  • Add your own hooks. A one-line code injection that adds id attributes to the sections you test against gives you stable targets you own.

  • Never test inside a gallery or slideshow block. They re-render on their own schedule and will overwrite your changes.

  • Re-check variations after any template or section change on the site, because nothing else will tell you.

Verifying the Integration

  1. View source on a live page and confirm the snippet is the first script in the head. Squarespace's preview mode does not always reflect injected code — check the published site.

  2. Submit each mapped form and watch the network tab for a request to logx.optimizely.com. No request means the form title does not match the allowlist key, which is usually a trailing space.

  3. Force a variation with the optimizely_x query parameter and confirm it applies on a hard load and after navigating away and back.

  4. Confirm no double counting by submitting one form twice in a session; you should see two events, not four.

Common Failure Modes

  • The snippet in footer injection. The most common installation mistake here, and it guarantees a flash of the original page.

  • A form renamed in the editor. The allowlist key stops matching, and conversions go to zero without an error anywhere.

  • Event names that do not exist in Optimizely. Pushed events are dropped silently.

  • Variations keyed to template class names. They break on a template update, months after anyone remembers building them.

  • Testing on a Personal plan. There is no header injection to install into, and workarounds all run too late to be worth it.

David Sertillange, Independent experimentation specialist
David Sertillange

Independent experimentation specialist

David Sertillange is an independent experimentation specialist with 10 years implementing Optimizely across enterprise programs. He specializes in Feature Experimentation, analytics integrations, and helping teams build a culture of data-driven decision making.

Subscribe

Practical Optimizely tips, monthly. No fluff.