Build Atomic commerce interfaces without using the Coveo app for Shopify

This is for:

Developer

This page explains how to integrate the Coveo Atomic search box, search page, product listing page, and recommendation components in your Shopify store. The details vary depending on the theme you use, but the core logic remains the same.

This article uses the default Dawn theme as its starting point.

Tip

This article assumes that you’re not using the Coveo AI Search & Discovery app, which allows for certain simplifications. If you’re using the app, see Build Atomic commerce interfaces in a Shopify store with the Coveo AI Search & Discovery app.

Supported versions

If you import the Coveo Atomic library through a CDN, you must use matching versions of the Atomic Commerce JavaScript module (https://static.cloud.coveo.com/atomic/v3/atomic.esm.js) and Coveo Shopify script (https://static.cloud.coveo.com/shopify/v1/headless/commerce.esm.js).

Tip

Check the commit details for your Atomic release in GitHub to find the matching version of the Coveo Shopify script.

Both must be major versions or fully qualified versions. Don’t mix a major version of one with a fully qualified version of the other. Using the major versions is preferable because you can automatically benefit from the latest releases.

The following table shows some examples of matching versions of the Coveo Atomic Commerce JavaScript module and Coveo Shopify script:

Version type Atomic Commerce JavaScript module Coveo Shopify script

Major version

/atomic/v3/

/shopify/v1/headless/

Fully qualified version[1]

/atomic/v3.48.0/

/shopify/v1.9.12/headless/

/atomic/v3.44.0/

/shopify/v1.9.11/headless/

1. The versions listed here are just examples. Be sure to check your specific Atomic version (or the latest Atomic release) for the matching Coveo Shopify script.

Load Atomic and build the Commerce engine

  1. In your Shopify theme editor, create a snippet named atomic.liquid and paste the following code. For optimization, this snippet relies on promises.

    <!-- snippets/atomic.liquid -->
    
    <script type="module">
      window.loadAtomic = function () {
        if (document.getElementById('atomic-script')) { 1
          return customElements.whenDefined('atomic-commerce-interface'); 2
        }
    
        var script = document.createElement('script');
        script.src = 'https://static.cloud.coveo.com/atomic/v3/atomic.esm.js'; 3
        script.type = 'module';
        script.setAttribute('id', 'atomic-script');
        document.head.appendChild(script);
        var css = document.createElement('link');
        css.rel = 'stylesheet';
        css.href = 'https://static.cloud.coveo.com/atomic/v3/themes/coveo.css'; 4
        document.head.appendChild(css);
    
        return customElements.whenDefined('atomic-commerce-interface');
      };
    </script>
    1 Checks whether the Atomic scripts have already been loaded.
    2 Returns a promise that resolves when the atomic-commerce-interface custom element is defined.
    3 The Atomic Commerce JavaScript module.
    4 The Atomic Commerce CSS file.
  2. Create a snippet named engine.liquid in which you paste the following code, replacing the placeholders with your own values.

    <!-- snippets/engine.liquid -->
    
    <script type="module">
      window.configureHeadless = async () => {
        const { buildShopifyCommerceEngine, buildCart } = await
          import('https://static.cloud.coveo.com/shopify/v1/headless/commerce.esm.js');
        const engine = buildShopifyCommerceEngine({
          commerceEngineOptions: {
            configuration: {
              accessToken: '<ACCESS_TOKEN>', 1
              organizationId: '<ORGANIZATION_ID>', 2
              analytics: {
                enabled: true,
                trackingId: '<TRACKING_ID>', 3
              },
              context: { 4
                country: {{ localization.country.iso_code | json }},
                currency: {{ localization.country.currency.iso_code | json }},
                view: {
                  url: {{ canonical_url | json }},
                },
                language: {{ request.locale.iso_code | json }},
                cart: {{ cart.items | json }}.map(function (item) {
                  return {
                    productId: `gid://shopify/ProductVariant/${item.variant_id}`,
                    name: item.title,
                    price: item.final_price,
                    quantity: item.quantity,
                  };
                }),
              },
            },
          },
        });
        window.CoveoCartController = buildCart(engine); 5
        return engine;
      };
    </script>
    1 An access token with the Search - Execute queries - Allowed and Analytics - Analytics data - Push privileges.
    2 Your organization ID.
    3 The target tracking ID.
    4 The context object containing the country, currency, view, language, and cart information, extracted from the Shopify store.
    5 Creates a cart controller instance by passing the initialized Atomic engine to the buildCart function, and assigns the result to window.CoveoCartController. Essential for keeping analytics and recommendations accurate when using the Shopify AJAX API, see Keep Atomic Cart State in sync with Shopify (AJAX use case) for details.

You’ll need to include this snippet in every page of your Shopify store in which you want to use Coveo Atomic Commerce components.

Note for non-AJAX users

If your storefront uses form based cart submissions, each page reload automatically re-initializes the Atomic engine with the updated cart state. No additional syncing is required in that case.

Keep Atomic Cart State in sync with Shopify (AJAX use case)

When using Shopify AJAX APIs to update the cart, update the Atomic cart controller to keep the search and analytics state in sync.

Note

Form based cart submissions which reload the page cause the Atomic engine to re-initialize with the updated cart state, so no additional work is required.

Sync the Atomic Cart Controller after AJAX cart updates

After any AJAX cart update, synchronize the Atomic cart controller with the latest Shopify cart state.

The following example demonstrates how to add an item to the Shopify cart using the /cart/add.js endpoint, then fetch the updated cart state to be synced with the cart controller.

Be sure to implement a similar routine after every AJAX cart change event in your storefront.

For more details on the AJAX cart API, see the Shopify cart API reference.

fetch('/cart/add.js', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({id: VARIANT_ID, quantity: 1})
})
  .then(response => response.json())
  .then(() => {
    fetch('/cart.js')
      .then(response => response.json())
      .then(cart => {
        const atomicCartItems = cart.items.map(item => ({
          productId: `gid://shopify/ProductVariant/${item.variant_id}`,
          name: item.title,
          price: item.final_price,
          quantity: item.quantity,
        }));

        window.CoveoCartController.setItems(atomicCartItems);
      });
  });
Important

Analytics and recommendations may become out of sync if they’re not updated after an AJAX cart change.

Initialize the web pixel

The Coveo Atomic library only logs click events, so you’ll need to use a web pixel to log cart events, product view events, and purchase events.

The web pixel must be initialized before it can log events to power your Coveo ML models. To initialize it, create an initialization snippet as follows and add it to every page of your Shopify store.

  1. Create a snippet named initialize-analytics.liquid and paste the following code:

    <!-- snippets/initialize-analytics.liquid -->
    
    <script type="module">
      import {init} from 'https://static.cloud.coveo.com/shopify/v1/headless/commerce.esm.js';
    
      init({ 1
        accessToken: '<ACCESS_TOKEN>', 2
        organizationId: '<ORGANIZATION_ID>', 3
        trackingId: '<TRACKING_ID>', 4
        clientId: '<CLIENT_ID>' 5
      });
    </script>
    1 The init function initializes the web pixel (see @coveo/shopify).
    2 An access token with the Analytics - Analytics data - Push privilege.
    3 Your organization ID.
    4 The target tracking ID.
    5 The target client ID.
  2. Include this snippet in your theme.liquid file, just before the closing </head> tag:

    {% render 'initialize-analytics' %}
  3. Verify your setup by loading a storefront page and confirming that cart events, product view events, and purchase events appear in your analytics.

Search page

To implement a Coveo Atomic search page or replace an existing search page with an Atomic one, do the following:

  1. Create a snippet named initialize-search-page.liquid to initialize the Atomic interface.

    <!-- snippets/initialize-search-page.liquid -->
    
    <script type="module">
      await window.loadAtomic();
      window.CoveoCommerceEngineForSearch = window.CoveoCommerceEngineForSearch || window.configureHeadless();
      const engine = await window.CoveoCommerceEngineForSearch;
      const searchPage = document.querySelector('#search-page');
      await searchPage.initializeWithEngine(engine); 1
      searchPage.executeFirstRequest(); 2
    </script>
    1 Initialize the search page with the Coveo engine.
    2 The executeFirstRequest method sends the first query to the Coveo engine, ensuring the search page is populated with results when it loads.
  2. Open your theme and locate the code that renders the search results page.

    Note

    In the Dawn theme, this code is located in sections/main-search.liquid.

  3. Remove the existing search page code from this file, if any exists.

  4. Add the initialization snippets and an atomic-commerce-interface element of type search that includes the target Atomic search page components, as follows:

    <!--- sections/main-search.liquid --->
    {% render 'engine' %}
    {% render 'atomic' %}
    {% render 'initialize-search-page' %}
    <atomic-commerce-interface id="search-page" type="search">
      <atomic-commerce-layout>
        <atomic-layout-section section="search">
          <atomic-commerce-search-box>
            <atomic-commerce-search-box-recent-queries></atomic-commerce-search-box-recent-queries>
            <atomic-commerce-search-box-query-suggestions></atomic-commerce-search-box-query-suggestions>
            <atomic-commerce-search-box-instant-products image-size="small">
              <atomic-product-template>
                <template>
                  <atomic-product-section-name>
                    <atomic-product-link></atomic-product-link>
                  </atomic-product-section-name>
                  <atomic-product-section-visual>
                    <atomic-product-field-condition if-defined="ec_thumbnails">1
                      <atomic-product-image field="ec_thumbnails"></atomic-product-image>
                    </atomic-product-field-condition>
                  </atomic-product-section-visual>
                  <atomic-product-section-metadata>
                    <atomic-product-field-condition if-defined="ec_brand">
                      <atomic-product-text field="ec_brand"></atomic-product-text>
                    </atomic-product-field-condition>
                    <atomic-product-field-condition if-defined="ec_rating">
                      <atomic-product-rating field="ec_rating"></atomic-product-rating>
                    </atomic-product-field-condition>
                  </atomic-product-section-metadata>
                  <atomic-product-section-emphasized>
                    <atomic-product-price></atomic-product-price>
                  </atomic-product-section-emphasized>
                  <atomic-product-section-children>
                    <atomic-product-children></atomic-product-children>
                  </atomic-product-section-children>
                </template>
              </atomic-product-template>
            </atomic-commerce-search-box-instant-products>
          </atomic-commerce-search-box>
        </atomic-layout-section>
        <atomic-layout-section section="facets"><atomic-commerce-facets></atomic-commerce-facets></atomic-layout-section>
        <atomic-layout-section section="main">
          <atomic-layout-section section="status">
            <atomic-commerce-breadbox></atomic-commerce-breadbox>
            <atomic-commerce-query-summary></atomic-commerce-query-summary>
            <atomic-commerce-sort-dropdown></atomic-commerce-sort-dropdown>
            <atomic-commerce-did-you-mean></atomic-commerce-did-you-mean>
            <atomic-commerce-refine-toggle></atomic-commerce-refine-toggle>
          </atomic-layout-section>
          <atomic-layout-section section="products">
            <atomic-commerce-product-list display="grid" density="compact" image-size="small">
              <atomic-product-template>
                <template>
                  <atomic-product-section-name id="product-name-section">
                    <atomic-product-link></atomic-product-link>
                  </atomic-product-section-name>
                  <atomic-product-section-visual>
                    <atomic-product-field-condition if-defined="ec_thumbnails">
                      <atomic-product-image field="ec_thumbnails"></atomic-product-image>
                    </atomic-product-field-condition>
                  </atomic-product-section-visual>
                  <atomic-product-section-metadata>
                    <atomic-product-field-condition if-defined="ec_brand">
                      <atomic-product-text field="ec_brand"></atomic-product-text>
                    </atomic-product-field-condition>
                    <atomic-product-field-condition if-defined="ec_rating">
                      <atomic-product-rating field="ec_rating"></atomic-product-rating>
                    </atomic-product-field-condition>
                  </atomic-product-section-metadata>
                  <atomic-product-section-emphasized>
                    <atomic-product-price></atomic-product-price>
                  </atomic-product-section-emphasized>
                  <atomic-product-section-description>
                    <atomic-product-excerpt></atomic-product-excerpt>
                  </atomic-product-section-description>
                  <atomic-product-section-children>
                    <atomic-product-children></atomic-product-children>
                  </atomic-product-section-children>
                </template>
              </atomic-product-template>
            </atomic-commerce-product-list>
            <atomic-commerce-query-error></atomic-commerce-query-error>
            <atomic-commerce-no-products></atomic-commerce-no-products>
          </atomic-layout-section>
          <atomic-layout-section section="pagination">
            <atomic-commerce-load-more-products></atomic-commerce-load-more-products>
          </atomic-layout-section>
        </atomic-layout-section>
      </atomic-commerce-layout>
    </atomic-commerce-interface>
    1 The ec_thumbnails standard commerce field contains the product images used by the image components in this example. Adjust this property if your product thumbnails are stored in a different field.
  5. Save your changes and preview the search page in your storefront to confirm that results and components render as expected.

You’ll probably want to include a standalone search box on every page of your website other than the search page, which already includes a search box. To implement it, do the following:

  1. Create a snippet named initialize-ssb.liquid to initialize the standalone search box.

    <!-- snippets/initialize-ssb.liquid -->
    
    <script type="module">
      window.handleCoveoSearchboxClick = async function () {
        await window.loadAtomic();
        window.CoveoCommerceEngineForSearch = window.CoveoCommerceEngineForSearch || window.configureHeadless();
        const engine = await window.CoveoCommerceEngineForSearch;
        const standaloneSearchBox = document.querySelector('#standalone-search-box');
        if (!standaloneSearchBox.hasAttribute('atomic-initialized')) {
          standaloneSearchBox.initializeWithEngine(engine);
          standaloneSearchBox.setAttribute('atomic-initialized', 'true'); 1
        }
      };
    </script>
    1 This ensures that the standalone search box is only initialized once.
  2. Open your theme and locate the code that renders search boxes.

    Note

    In the Dawn theme, this code is located in sections/header-search.liquid.

  3. Remove the existing search box code from this file.

  4. Add the following code:

    <!--- snippets/header-search.liquid --->
    {% if request.path != '/search' %}1
    {% render 'atomic' %}
    {% render 'engine' %}
    {% render 'initialize-ssb' %}
    
    <details-modal>
      <details>
        <summary
          class="header__icon header__icon--search header__icon--summary link focus-inset modal__toggle"
          aria-haspopup="dialog"
        >
          <button id="search-button" aria-label="Search" class="svg-wrapper" onclick="handleCoveoSearchboxClick()">
            <span class="svg-wrapper">
              {{- 'icon-search.svg' | inline_asset_content -}} 2
            </span>
          </button>
        </summary>
        <div
          class="search-modal modal__content gradient"
          role="dialog"
          aria-modal="true"
        >
          <div class="modal-overlay"></div>
          <div
            tabindex="-1"
            class="standalone-search-box-container"
          >
            <atomic-commerce-interface type="search" id="standalone-search-box">
              <atomic-layout-section section="search">
                <atomic-commerce-search-box redirection-url="/search">
                  <atomic-commerce-search-box-recent-queries></atomic-commerce-search-box-recent-queries>
                  <atomic-commerce-search-box-query-suggestions></atomic-commerce-search-box-query-suggestions>
                  <atomic-commerce-search-box-instant-products image-size="small">
                    <atomic-product-template>
                      <template>
                        <atomic-product-section-name>
                          <atomic-product-link></atomic-product-link>
                        </atomic-product-section-name>
                        <atomic-product-section-visual>
                          <atomic-product-image field="ec_thumbnails"></atomic-product-image>
                        </atomic-product-section-visual>
                        <atomic-product-section-metadata>
                          <atomic-product-text field="ec_brand"></atomic-product-text>
                          <atomic-product-rating field="ec_rating"></atomic-product-rating>
                        </atomic-product-section-metadata>
                        <atomic-product-section-emphasized>
                          <atomic-product-price></atomic-product-price>
                        </atomic-product-section-emphasized>
                        <atomic-product-section-children>
                          <atomic-product-children></atomic-product-children>
                        </atomic-product-section-children>
                      </template>
                    </atomic-product-template>
                  </atomic-commerce-search-box-instant-products>
                </atomic-commerce-search-box>
              </atomic-layout-section>
            </atomic-commerce-interface>
            <div class="hidden"><input></div> 3
            <button
              type="button"
              class="search-modal__close-button modal__close-button link link--text focus-inset"
              aria-label="{{ 'accessibility.close' | t }}"
            >
              <span class="svg-wrapper">
                {{- 'icon-close.svg' | inline_asset_content -}}
              </span>
            </button>
          </div>
        </div>
      </details>
    </details-modal>
    {% endif %}
    1 Don’t display the standalone search box on the search page, since it already includes a search box.
    2 This image is from the Dawn theme. Replace it with the appropriate SVG icon for your theme, as needed.
    3 This div and the following button are carried over from the Dawn theme for modal handling. Customize this markup to fit your theme, as needed.
  5. Style your standalone search box. The CSS classes and styles you apply to your standalone search box depend on your theme.

    1. In the Dawn theme, create an ssb.css file in the assets folder and include the following:

      /* assets/ssb.css */
      
      .standalone-search-box-container {
          width: 80%;
          display: flex;
          justify-content: center;
      }
      
      #search-button {
          background: white;
          border: white;
          cursor: pointer;
      }
      
      #search-button:hover {
          transform: scale(1.07);
      }
    2. Add the following to render the CSS in header-search.liquid:

      {{ 'ssb.css' | asset_url | stylesheet_tag }}
  6. Save your changes and preview the standalone search box in your storefront to confirm that it renders as expected.

Product listing page

To implement Coveo Atomic PLPs or replace existing PLPs with Atomic ones, do the following:

  1. Create the PLPs using the Public Listing Page API.

  2. Set up facets for your PLPs using the Facet manager in the Coveo Merchandising Hub (CMH).

  3. Create query configurations using the Query Configurations API. These set certain parameters for your PLPs, such as sort criteria and additional fields.

  4. Create a snippet named initialize-plp.liquid to initialize the Atomic interface.

    <!-- snippets/initialize-plp.liquid -->
    <script type="module">
      await window.loadAtomic();
      const engine = await window.configureHeadless();
      const productList = document.querySelector('#product-listing');
      await productList.initializeWithEngine(engine);
      productList.executeFirstRequest();
    </script>
  5. Open your theme and locate the code that renders PLPs.

    Note

    In the Dawn theme, an example file with this code is sections/collection-template.liquid.

  6. Remove the existing PLP code from this file.

  7. Add the initialization snippets and an atomic-commerce-interface element of type product-listing that includes the target Atomic PLP components, as follows:

    {% render 'engine' %}
    {% render 'atomic' %}
    {% render 'initialize-plp' %}
    
    <atomic-commerce-interface id="product-listing" type="product-listing">
      <atomic-commerce-layout>
        <atomic-layout-section section="facets"><atomic-commerce-facets></atomic-commerce-facets></atomic-layout-section>
        <atomic-layout-section section="main">
          <atomic-layout-section section="status">
            <atomic-commerce-breadbox></atomic-commerce-breadbox>
            <atomic-commerce-query-summary></atomic-commerce-query-summary>
            <atomic-commerce-sort-dropdown></atomic-commerce-sort-dropdown>
            <atomic-commerce-refine-toggle></atomic-commerce-refine-toggle>
          </atomic-layout-section>
          <atomic-layout-section section="products">
            <atomic-commerce-product-list display="grid" density="compact" image-size="small">
              <atomic-product-template>
                <template>
                  <atomic-product-section-name>
                    <atomic-product-link></atomic-product-link>
                  </atomic-product-section-name>
                  <atomic-product-section-visual>
                    <atomic-product-image field="ec_thumbnails"></atomic-product-image>
                  </atomic-product-section-visual>
                  <atomic-product-section-children>
                    <atomic-product-children></atomic-product-children>
                  </atomic-product-section-children>
                  <atomic-product-section-metadata>
                    <atomic-product-text field="ec_brand"></atomic-product-text>
                    <atomic-product-rating field="ec_rating"></atomic-product-rating>
                  </atomic-product-section-metadata>
                  <atomic-product-section-emphasized>
                    <atomic-product-price></atomic-product-price>
                  </atomic-product-section-emphasized>
                  <atomic-product-section-description>
                    <atomic-product-description></atomic-product-description>
                  </atomic-product-section-description>
                </template>
              </atomic-product-template>
            </atomic-commerce-product-list>
            <atomic-commerce-query-error></atomic-commerce-query-error>
            <atomic-commerce-no-products></atomic-commerce-no-products>
          </atomic-layout-section>
          <atomic-layout-section section="pagination">
            <atomic-commerce-load-more-products></atomic-commerce-load-more-products>
          </atomic-layout-section>
        </atomic-layout-section>
      </atomic-commerce-layout>
    </atomic-commerce-interface>
  8. Save your changes and preview the PLPs in your storefront to confirm that products and components render correctly.

Recommendations

To implement Coveo Atomic recommendations or replace your existing recommendations, do the following:

  1. Create the target recommendation configurations. Currently, the Coveo AI Search & Discovery app doesn’t create recommendation configurations automatically. You must create them yourself, regardless of whether you’re using the app.

  2. Create a snippet named initialize-recs.liquid to initialize the Atomic interface.

    <!-- snippets/initialize-recs.liquid -->
    
    <script type="module">
      await window.loadAtomic();
      window.CoveoCommerceEngineForSearch = window.CoveoCommerceEngineForSearch || window.configureHeadless());
      const engine = await window.CoveoCommerceEngineForSearch;
      const recs = document.querySelector('#recs-component');
      recs.initializeWithEngine(engine);
    </script>
  3. Open your theme and locate the code that renders recommendations.

    Note

    In the Dawn theme, an example file with this code is featured-collection.liquid.

  4. Remove the existing recommendation code from this file.

  5. Add the initialization snippets and an atomic-commerce-recommendation-interface element that includes the target Atomic recommendation components, as follows:

    {% render 'engine' %}
    {% render 'atomic' %}
    {% render 'initialize-recs' %}
    
    <atomic-commerce-recommendation-interface class="page-width" id="recs-component">
      <atomic-commerce-layout>
        <atomic-layout-section section="main">
          <atomic-commerce-recommendation-list
            slot-id=<SLOT_ID> 1
          >
            <atomic-product-template>
              <template>
                <atomic-product-section-name>
                  <atomic-product-link></atomic-product-link>
                </atomic-product-section-name>
                <atomic-product-section-visual>
                  <atomic-product-image field="ec_thumbnails"></atomic-product-image>
                </atomic-product-section-visual>
                <atomic-product-section-metadata>
                  <atomic-product-field-condition if-defined="ec_brand">
                    <atomic-product-text field="ec_brand"></atomic-product-text>
                  </atomic-product-field-condition>
                  <atomic-product-field-condition if-defined="ec_brand">
                    <atomic-product-rating field="ec_rating"></atomic-product-rating>
                  </atomic-product-field-condition>
                </atomic-product-section-metadata>
                <atomic-product-section-description>
                  <atomic-product-text field="ec_shortdesc"></atomic-product-text>
                </atomic-product-section-description>
                <atomic-product-section-emphasized>
                  <atomic-product-price></atomic-product-price>
                </atomic-product-section-emphasized>
                <atomic-product-section-children>
                  <atomic-product-children></atomic-product-children>
                </atomic-product-section-children>
              </template>
            </atomic-product-template>
          </atomic-commerce-recommendation-list>
        </atomic-layout-section>
      </atomic-commerce-layout>
    </atomic-commerce-recommendation-interface>
    1 Replace <SLOT_ID> with the target slot ID for your recommendations. The slot ID is returned when you create recommendation configurations.
  6. Save your changes and preview the recommendations in your storefront to confirm that they render as expected.

What’s next?

To explore the available Coveo Atomic Commerce components and options, see the reference documentation.

To learn more about the Atomic library, see the Atomic documentation.