# Product lists without the CMS

> Best sellers, newest products, related and complementary products, a custom query, one collection or the products in a metafield, filled straight from Shopify with one attribute on smootify-product.

Status: Ready (Smootify 2.0)  
1.x page: https://docs.smootify.io/docs/ecommerce/products/listing  
URL: https://www.smootify.io/docs/build/product-lists  
Updated: 2026-10-13

Some lists cannot come from the Webflow CMS: best sellers need Shopify’s sales history, and related products are computed by Shopify. For these, a `smootify-product` fills itself from Shopify. You design one card, give it a `data-id` value, and Smootify repeats it once per product. There is no Collection list and no sync.

> [!NOTE]
> Before you start, Smootify must be installed on the site. These lists read Shopify directly, so the products do not need to be in the CMS.

## How it works

1. Add a Div block for the grid, or a Webflow Slider.
2. Inside it, add a `smootify-product` and design it as a [product card](https://www.smootify.io/docs/build/product-cards).
3. Give it the custom attribute `data-id` with one of the values below, and `limit` for the number of products.

```html
<div>
  <h2>Best sellers</h2>
  <div>
    <smootify-product data-id="best-seller" limit="8">
      <a product="url" href="#">Product name</a>
      <smootify-price><div data-prop="price">$0.00</div></smootify-price>
    </smootify-product>
  </div>
</div>
```

When the products arrive, the card takes the first one, and Smootify places a copy of the card after it for each of the others. In a Webflow Slider, each product gets its own slide (see below).

**How many.** Without `limit`, a list shows 3 products. Related and complementary products are the exception: without `limit` they show all the products Shopify returns. A metafield list always shows all the products picked in it.

**When the list is empty.** If Shopify returns no product, the card is removed. The heading you put next to it stays, unless you hide it: see “Hide a section when its list is empty” below.

## The lists

Each `data-id` fills the card from a different place:

| `data-id` | What it shows | Also needs |
|---|---|---|
| `best-seller` | the best sellers, over the whole life of the store | nothing |
| `created-at` | the newest products, by their Shopify creation date | nothing |
| `purchasable` | products that can be bought now | nothing |
| `custom-query` | products that match a Shopify search | `data-query`, optionally `data-sort` |
| `specific-collection` | the products of one collection, in its Shopify order | `data-collection-handle` |
| `related` | products Shopify finds related to a product | `data-parent-id` |
| `complementary` | products that go with a product | `data-parent-id` |
| `metafields.custom.pairs_with` | the products picked in a metafield of a product | `data-parent-id` |
| `metafields.custom.my_products` | the products picked in a metafield of the customer | `data-parent-id="customer"` |

### Best sellers, newest, available

`best-seller`, `created-at` and `purchasable` need nothing else. The first two use data Webflow never receives: the store’s sales history, and the date each product was created in Shopify (not in Webflow).

### A custom query

`data-id="custom-query"` lists the products that match `data-query`, written in Shopify’s [product search syntax](https://shopify.dev/docs/api/storefront/latest/queries/products#arguments-query):

- `tag:gift` for a tag;
- `vendor:Acme` for a vendor;
- `product_type:Shoes` for a product type;
- `variants.price:<50` for a price range.

`data-sort` sets the order with one of Shopify’s [product sort keys](https://shopify.dev/docs/api/storefront/latest/enums/ProductSortKeys), such as `TITLE`, `PRICE` or `CREATED_AT`. The order runs one way only: `PRICE` is the lowest price first.

### One collection

`data-id="specific-collection"` with `data-collection-handle="summer"` lists the products of that collection, in the order set in Shopify. It is useful where the page is not the collection’s own page, such as the home page.

Inside a Collection list of your **Collections**, bind `data-collection-handle` to the **Slug** field: each collection item then shows its own products. A Webflow Collection list cannot do this with a nested list of products.

### Related and complementary products

On the product page, `data-id="related"` or `data-id="complementary"` lists products for the product in `data-parent-id`. Bind `data-parent-id` to the **Shopify ID** field of the page.

```html
<div condition="has-related">
  <h2>You may also like</h2>
  <div class="w-slider">
    <div class="w-slider-mask">
      <div class="w-slide">
        <smootify-product data-id="related" data-parent-id="{Shopify ID}" limit="6"><a product="url" href="#">Product name</a></smootify-product>
      </div>
      <div class="w-slide">
        <smootify-product data-id="loader"><a product="url" href="#">Product name</a></smootify-product>
      </div>
    </div>
  </div>
</div>
```

- Shopify computes related products by itself.
- Complementary products are the ones you pick for each product in Shopify’s **Search & Discovery** app. If none are set there, the list stays empty.
- Without `data-parent-id`, the element is removed.

For the same lists in the cart, based on what the visitor added, see [Cart upsells](https://www.smootify.io/docs/build/cart-upsells).

### Products picked in a metafield

`data-id="metafields.custom.pairs_with"` lists the products of the product metafield `custom.pairs_with` of the product in `data-parent-id`. Write the namespace and the key of your own metafield after `metafields.`.

- The metafield must be of type product reference, or a list of product references. Any other type gives no products.
- Its definition in Shopify needs **Storefront access**.
- `data-repeat="2"` repeats the list twice, which helps a slider that has only a few products.

The merchant then picks the products for each product in Shopify, such as “Complete the look”. To show the referenced products without prices or add to cart, the `metafield` attribute is lighter: see [Metafields on the page](https://www.smootify.io/docs/build/metafields).

### Alternatives when a variant is sold out

Instead of a dead end, the product page can offer other products while the chosen variant cannot be bought. It takes only attributes on the page’s `smootify-product`: nothing goes into the Webflow CMS.

1. In Shopify, create a **variant** metafield of type **List of products**, such as `custom.alternatives`, with **Storefront access**. On each variant that needs them, pick its alternatives: a sold-out size can point to similar products.
2. Inside the page’s `smootify-product`, add a Div block with `condition="out-of-stock"`. It shows only while the chosen variant cannot be bought.
3. Inside it, add a Div block with `if-variant-metafield="alternatives"` and put your heading in it. It shows only when the chosen variant has alternatives.
4. Inside that Div block, add a Link block with `variant-metafield="alternatives"`, holding an Image with `reference="image"` and a Text block with `reference="title"`. It repeats once per alternative.

```html
<smootify-product data-id="{Shopify ID}">
  <div condition="out-of-stock">
    <div if-variant-metafield="alternatives">
      <h2>Sold out. You may like these instead</h2>
      <a href="#" class="w-inline-block" variant-metafield="alternatives">
        <img reference="image">
        <div reference="title">Product</div>
      </a>
    </div>
  </div>
</smootify-product>
```

Everything follows the chosen variant: the block shows for a sold-out size that has alternatives, changes with the size, and hides when the visitor picks a size in stock. More about references and `if-variant-metafield`: [Metafields on the page](https://www.smootify.io/docs/build/metafields).

These links show the image and the title of each alternative. For cards with a price and an add-to-cart button, use a **product** metafield with a `smootify-product` list, as above, inside the same `condition="out-of-stock"` block: the alternatives are then the same for every variant, and `if-metafield="alternatives"` on the Div block around the heading hides it when the product has none.

### Products picked for each customer

With `data-parent-id="customer"`, the same `data-id="metafields.<namespace>.<key>"` lists the products of a metafield of the logged-in customer: a list the merchant curates for each customer, such as their usual order or the catalog of a wholesale buyer.

```html
<smootify-product data-id="metafields.custom.my_products" data-parent-id="customer">
  <div product="title">Product name</div>
  <smootify-price><div data-prop="price">$0.00</div></smootify-price>
</smootify-product>
```

- The metafield is a customer metafield, of type product reference or a list of product references. Create it in Shopify under **Settings › Custom data › Customers**, with its **Customer accounts** access set to **Read**, then pick the products on each customer.
- Customer accounts must be set up on the site: see [Customer accounts](https://www.smootify.io/docs/build/account).
- For a visitor who is not logged in, or a customer with no products in the metafield, the element is removed.
- `data-repeat` works as for a product metafield. While the list has products, the page’s `body` gets the class `has-customer-` followed by the namespace and key, such as `has-customer-custom-my_products`.

## Lists in a slider

Put the `smootify-product` in the first slide of a Webflow Slider. Each product after the first goes into its own slide.

**Placeholder slides.** In the slides after the first, you can design `smootify-product` elements with `data-id="loader"`, as in the example above. The list fills them in order, one per product, so the slider has its final shape before the products load. When there are more products than placeholders, Smootify adds copies of the first slide. Placeholders the list does not reach stay as designed: they are part of the layout, not leftovers. See [placeholder cards](https://www.smootify.io/docs/build/product-cards).

**The slider follows the products.** Smootify redraws the Webflow Slider once the slides are filled, and again when they change. A card that hides, such as one with `hide-if-product-in-cart` whose product is in the cart, hides its whole slide, so the slider leaves no gap. When every card fits in view, with one page, both arrows get the class `is-disabled` and `aria-disabled="true"`: style `is-disabled` as a combo class on the arrows, for example with lower opacity. At the ends of a slider that is not infinite, Webflow’s own **Hide arrows** setting hides them.

## Hide a section when its list is empty

While a list has products, the page’s `body` gets the class `has-` followed by the list’s value: `has-best-seller`, `has-related`, and so on. For a metafield list, the class is `has-` with the namespace and key, such as `has-custom-pairs_with`.

For related and complementary products, two conditions do the work in the Designer: an element with `condition="has-related"` or `condition="has-complementary"` shows only while that list has products. Put it on the section around the heading and the list, as in the example above. For the other lists, the `body` class can be used in custom CSS: see [CSS](https://www.smootify.io/docs/developers/css).

## Other lists

- The visitor’s wishlist: see [Wishlist](https://www.smootify.io/docs/features/wishlist).
- Recently viewed products: see [Recently viewed](https://www.smootify.io/docs/features/last-viewed).
- Filtered lists the visitor can change: see [Filters, sorting and pagination](https://www.smootify.io/docs/build/filters).

## Before you publish

> [!STUCK]
> - **Only 3 products show.** That is the default. Add `limit` with the number you want.
> - **Related or complementary products disappear.** `data-parent-id` is missing or empty. Bind it to the `cms:Shopify ID` field.
> - **Complementary products never show.** No complementary products are set for that product in Shopify's Search & Discovery app.
> - **The metafield list is empty.** The metafield is not a product reference, or its definition has no Storefront access.
> - **The customer's list never shows.** The visitor is not logged in, the metafield is not a customer metafield, or its Customer accounts access is off.
> - **The heading stays when the list is empty.** Put `condition="has-related"` or `condition="has-complementary"` on the section, or use the `body` class for the other lists.
