Build Atomic commerce interfaces with 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 using the Coveo AI Search & Discovery app, which allows for certain simplifications. If that’s not the case, see Build Atomic commerce interfaces in a Shopify store without the Coveo AI Search & Discovery app.

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.

    <!-- snippets/engine.liquid -->
    
    <script type="module">
      window.configureHeadless = async (config) => {
        const {buildShopifyCommerceEngine} =
          await import('https://static.cloud.coveo.com/shopify/v1/headless/commerce.esm.js');
        if (window.CoveoEngine) {
          if (!window.CoveoCartController) {
            window.CoveoCartController = window.CoveoEngine.buildCart(); 1
          }
          return window.CoveoEngine;
        }
        window.CoveoEngine = buildShopifyCommerceEngine({
          commerceEngineOptions: {
            configuration: { 2
              accessToken: config.accessToken,
              organizationId: config.organizationId,
              analytics: {
                enabled: true,
                trackingId: config.trackingId,
              },
              context: { 3
                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 = window.CoveoEngine.buildCart();
        return window.CoveoEngine;
      };
    </script>
    1 Create a cart controller instance from the initialized Atomic engine and assigns it 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.
    2 Retrieve the access token, organization ID and tracking ID from a config object you’ll pass in the next steps.
    3 The context object containing the country, currency, view, language, and cart information, extracted from the Shopify store.

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 the Coveo AI Search & Discovery app uses 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, fetchAppProxyConfig} from 'https://static.cloud.coveo.com/shopify/v1/headless/commerce.esm.js';
    
      const config = await fetchAppProxyConfig({ 1
        marketId: {{ localization.market.id | encode_url_component }},
      });
    
      init(config); 2
    </script>
    1 The Coveo Shopify app contains an App Proxy endpoint that returns an object containing the access token, organization ID and tracking ID, which are required to initialize the web pixel.
    2 The init function initializes the web pixel with the configuration returned by the App Proxy endpoint (see @coveo/shopify).
  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">
      const [response] = await Promise.all([
        fetch('/apps/coveo?marketId={{ localization.market.id | encode_url_component }}'), 1
        window.loadAtomic(),
      ]);
      const config = await response.json();
      const engine = await window.configureHeadless(config);
      const searchPage = document.querySelector('#search-page');
      await searchPage.initializeWithEngine(engine); 2
      searchPage.executeFirstRequest(); 3
    </script>
    1 The Coveo Shopify app contains an App Proxy endpoint that returns an object containing the access token, organization ID, and tracking ID, which are required to build the Commerce engine.
    2 Initialize the search page with the Coveo engine.
    3 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 () {
        const [response] = await Promise.all([
          fetch('/apps/coveo?marketId={{ localization.market.id | encode_url_component }}'),
          window.loadAtomic(),
        ]);
        const config = await response.json();
        const engine = await window.configureHeadless(config);
        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 a Coveo Atomic product listing page (PLP) or replace an existing PLP with an Atomic one, do the following:

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

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

    <!-- snippets/initialize-plp.liquid -->
    <script type="module">
      const [response] = await Promise.all([
        fetch('/apps/coveo?marketId={{ localization.market.id | encode_url_component }}'),
        window.loadAtomic(),
      ]);
      const config = await response.json();
      const engine = await window.configureHeadless(config);
      const productList = document.querySelector('#product-listing');
      await productList.initializeWithEngine(engine);
      productList.executeFirstRequest();
    </script>
  3. Open your theme and locate the code that renders product listing pages.

    Note

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

  4. Remove the existing product listing page code from this file.

  5. Add the initialization snippets and an atomic-commerce-interface element of type product-listing that includes the target Atomic product listing page 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>
  6. Save your changes and preview the product listing page in your storefront to confirm that products and components render as expected.

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">
      const [response] = await Promise.all([
        fetch('/apps/coveo?marketId={{ localization.market.id | encode_url_component }}'),
        window.loadAtomic()
      ]);
      const config = await response.json();
      const engine = await window.configureHeadless(config);
      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.