Commmerce Docs

Overview

<mmm-data-source> is an SSR-only marker. The visual editor places it in your page JSON. The server, during render, walks the page tree, finds every <mmm-data-source>, calls the storefront API with the node's payload-* attributes plus URL state (filter, sort, page, category), and inlines the response into the page head as window.PRODUCT_RESPONSE_<KEY>.

Children — <mmm-loop>, <mmm-product-filter>, <mmm-product-pagination>, <mmm-product-card> — read their data straight from the inlined global via their own data="{{window.PRODUCT_RESPONSE_<KEY>.path}}" attributes. No fetch, no shadow DOM, no template substitution at runtime.

Filter / sort / pagination / category clicks become URL navigations which re-enter the SSR pipeline and produce a fresh response. This keeps every page fully indexable and lets you render category and search pages at the edge.

When to use

  • Collection / category landing pages that need a server-rendered product list.
  • Search-result pages where the query is in the URL.
  • Faceted-filter pages where filters re-render via URL change.
  • Wrapping <mmm-loop> so it has a response to iterate over.
  • Any spot where you want SEO-clean data already in the HTML on first paint.

Attributes

Read by the SSR walker, not by the element itself. Casing matters — payload keys are converted from kebab-case to camelCase before being sent to the SDK.

AttributeTypeDefaultDescription
apistringrequiredDot-path of the SDK method to call. Example: product.getProductList, category.getCategory, search.getSearchResults.
response-keystringrequiredIdentifier appended (upper-cased) to PRODUCT_RESPONSE_ to form the global window variable holding the response. Use one per data source per page.
payload-*stringEvery payload-foo-bar attribute becomes { fooBar: value } on the SDK call payload. Use for static knobs — page size, sort order, included fields.
data-typestringWhen set to category, the SSR pipeline extracts the category slug from the URL path if no payload-category is provided. Lets you drop one data source on the category template and have it work for every category page.
idstringStandard HTML id. Useful for the editor's selection tools.
classstringStandard HTML class list. The element itself is unstyled.
hiddenbooleanfalseHide the marker from the layout. Has no effect on SSR data fetching.

Events

One legacy window event is dispatched on script load for any code still listening from the v1 era.

EventTargetPayload
commmerce:data-source-readywindow{ version: '2.0.0' }

Examples

1. Plain product list

A product grid for the home page. The SSR walker calls product.getProductList({ pageSize: 12 }) and inlines the result as window.PRODUCT_RESPONSE_HOME_TOP.

<mmm-data-source
  api="product.getProductList"
  response-key="home-top"
  payload-page-size="12">
  <mmm-loop data="{{window.PRODUCT_RESPONSE_HOME_TOP.products}}">
    <mmm-product-card data-product="{{item}}"></mmm-product-card>
  </mmm-loop>
</mmm-data-source>

2. Category page (URL-driven)

One copy of the data source serves every category page. The data-type="category" hint tells SSR to pull the category slug from the URL when no payload is given.

<mmm-data-source
  api="category.getCategory"
  response-key="category"
  data-type="category"
  payload-page-size="24"
  payload-include-filters="true">
  <mmm-product-filter data="{{window.PRODUCT_RESPONSE_CATEGORY.filters}}"></mmm-product-filter>
  <mmm-loop data="{{window.PRODUCT_RESPONSE_CATEGORY.products}}">
    <mmm-product-card data-product="{{item}}"></mmm-product-card>
  </mmm-loop>
  <mmm-product-pagination data="{{window.PRODUCT_RESPONSE_CATEGORY.pagination}}"></mmm-product-pagination>
</mmm-data-source>

3. Multiple data sources on one page

"Best sellers" and "New arrivals" on the same template. Each gets its own response-key so the two responses do not collide.

<section>
  <h2>Best sellers</h2>
  <mmm-data-source
    api="product.getProductList"
    response-key="best-sellers"
    payload-sort="best-sellers"
    payload-page-size="8">
    <mmm-loop data="{{window.PRODUCT_RESPONSE_BEST_SELLERS.products}}">
      <mmm-product-card data-product="{{item}}"></mmm-product-card>
    </mmm-loop>
  </mmm-data-source>
</section>

<section>
  <h2>New arrivals</h2>
  <mmm-data-source
    api="product.getProductList"
    response-key="new-arrivals"
    payload-sort="newest"
    payload-page-size="8">
    <mmm-loop data="{{window.PRODUCT_RESPONSE_NEW_ARRIVALS.products}}">
      <mmm-product-card data-product="{{item}}"></mmm-product-card>
    </mmm-loop>
  </mmm-data-source>
</section>

Notes

  • This element does not fetch at runtime. If you serve the page without the SSR pipeline (e.g. preview a static export), window.PRODUCT_RESPONSE_* is undefined and child components render nothing.
  • response-key is normalised to UPPER_SNAKE_CASE before being appended to PRODUCT_RESPONSE_. best-sellerswindow.PRODUCT_RESPONSE_BEST_SELLERS.
  • Keep response-key unique per page. Two sources writing to the same key will overwrite each other.
  • The element is intentionally display: contents — it adds no box to your layout.