Skip to main content
This page is aimed at a merchant’s theme developer: the person editing the collection section, pagination snippet and facets of the Shopify theme.

Overview

Shopify collections have a single global product order that applies to all markets. To merchandise each market differently, Depict stores market-specific orderings in depict_multi_market_collection metaobjects, keyed by collection ID and market ID. This integration makes your collection section read products from those metaobjects instead of the collection’s default order. A collection version test rides on the same integration: Depict switches the test on for stores that have steps 1 to 5 in place. Collection version tests lists what a test needs from the theme. The same integration also supports hiding out-of-stock (OOS) products from collection pages while keeping their product pages reachable. This is controlled by two metafields set from the Depict portal: one on the shop (hide globally) and one per collection.
The $app:collection-settings / hide_oos metafield definition needs storefront access PUBLIC_READ. Without it these Liquid reads return nil and OOS hiding silently stays off.

Implementation

Update your existing main-collection.liquid (or equivalent collection section) as follows. The snippets use Dawn’s structure; adapt the markup to your theme.

1. Add market detection logic

Place this at the top of the section, outside the {% paginate %} block. It resolves the per-market metaobject, reads the two hide-OOS metafields, and skips Depict ordering whenever filters or a custom sort are active.
The lookup falls back to collection_{id}_market_fallback, which covers the main/default market and any market that has not been merchandised.

2. Compute the real page count

When OOS products are hidden, Shopify’s native paginate object (based on collection.products) over-counts, because hidden products still count toward the total. The depict_multi_market_collection wrapper carries an in_stock_counts JSON field that maps every enabled market ID to that market’s in-stock product count, for example {"93309862172":3000,"93309894940":3000}. Each count is the number of products in the collection that are storefront-visible (status ACTIVE) and in stock for that market. It is always less than or equal to collection.products_count. A larger count is a Depict-side bug: report it rather than working around it in the theme.
In-stock counts are currently enabled per merchant; rollout to all multi-market merchants is pending a canary week. The old depict_collection_data metaobject is dead. Do not reference it in new integrations.
This block must run before the render loop in step 3, because the loop uses depict_in_stock_count. Resolve multi_market_collection and the count at the top of the section (no paginate context needed), but compute metaobject_pages inside the {% paginate %} block: paginate.page_size is nil outside it and dividing by nil silently yields a wrong page count.
  • localization.market.id renders as a plain number; the | append: '' string normalisation is required for keying the JSON hash.
  • section.settings.products_per_page is Dawn’s page size. In general, divide by whatever feeds your outer paginate.
  • If the field or the market key is missing, everything degrades to native pagination (nil becomes blank and the override goes unused).
blank is truthy in Liquid and blank | times: 1 is 0. Never “blank out” the count to disable the feature: it renders zero products. To disable, remove or skip this assign block entirely.

3. Add custom product rendering logic

Products render from the chunked metaobject arrays (products_0, products_1, …), manually tracking which ones fall inside the current page window. Replace the product rendering loop with the following. The scan cap lines are explained right after the snippet.
What this does:
  • With OOS hiding on, only available products count toward the page offset and index.
  • It breaks out of the loop as soon as the current page window is filled (product_index >= end_paginate_offset), even when OOS hiding is on, so it never walks the full product set.
  • if products.count == 0 break is safe because Liquid drops references it cannot resolve, so an all-invisible chunk reads as empty and ends the walk early. Depict guarantees (since August 2026) that synced orders contain only storefront-visible products. If a collection stops rendering midway while deeper pages exist, suspect unresolvable references and report it to Depict.
Never wrap the end_paginate_offset breaks in {% unless depict_hide_oos %}. That forces the loop to hydrate .available on every reference (around 5,120 in large collections) just to count, producing multi-second cold renders. The real page count comes from the precomputed in-stock count instead.

The scan cap is mandatory

depict_scan_cap caps the scan at the in-stock count so requests never walk the OOS tail. It does two things:
  1. Skips the walk entirely on pages past the last real page (the {% unless … current_offset >= depict_scan_cap %} wrapper).
  2. Stops at the count-th in-stock match (the extra break at both existing break points).
Without the cap, requests past the real last page hydrate every reference to render nothing, around 2.9 seconds on a 4,000-product collection. A crawler doing this once caused a storefront-wide rate-limit fence, and ghost URLs stay crawler-discoverable via Shopify’s auto-injected <link rel="next">.

4. Fix pagination for OOS filtering

Feed metaobject_pages from step 2 through your theme’s pagination plumbing. When the count is unavailable, metaobject_pages stays blank and everything falls through to the native values.

Infinite scroll

If your theme uses infinite scroll, find where it sets the total page count on a data attribute (for example data-last-page) and override it:

Pagination buttons

If your theme renders pagination buttons, pass metaobject_pages as a total_pages_override parameter to the pagination snippet:
Then modify the snippet to respect the override. paginate.parts and paginate.pages reflect the unfiltered collection size, so three things need adjusting.
  1. Compute the effective total at the top of the snippet, and use effective_pages instead of paginate.pages wherever the total is referenced (visibility checks, mobile-hide logic, and so on):
  2. Skip page links beyond the real total. Inside the loop over paginate.parts, add an early skip:
    Ellipsis (…) parts past the effective last page survive this pruning because they are not links. This is cosmetic only.
  3. Fix the “next” arrow. The native paginate.next may point to a page beyond the real total, so add a check on the current page:

5. Fix the total products count

Themes show a product count in the filters bar or facets area (Dawn: snippets/facets.liquid). With OOS hiding active, collection.products_count still includes the hidden products. Use the in-stock count when it applies:
Pass total_products_to_display wherever the total is shown: the filters bar, the load-more “X of Y” text, and any results header.
The filters bar renders before {% paginate %}. Resolve multi_market_collection and depict_in_stock_count at the top of the section, and compute metaobject_pages inside the paginate block.

Collection version tests

A collection version test pits two saved versions of one collection against each other. Depict’s app embed serves the test and reorders the grid in the shopper’s browser; the section needs no extra Liquid. Depict switches the test on per store once steps 1 to 5 are in place, so market versions and tests render the same way.

How a test reaches the storefront

When a test starts, Depict writes each variant’s order as a depict_market_products metaobject and, for every market in scope, a depict_multi_market_collection metaobject with the handle collection_{collection id}_market_{market id}_abtest_{test id}_variant_A (and _variant_B) whose product_order points at it. These sit next to the market’s own collection_{collection id}_market_{market id} object and never replace it. On every collection page view the Depict Brand Features app embed reads the collection’s running test, resolves both variants for localization.market.id, and hands them to the Depict script. The script assigns the shopper to a group once per browser, fetches that variant’s whole order from the Storefront API, and reorders the product cards your section rendered for the page. New tests also serve each variant’s content cards, product duplicates and image settings. A running test outranks the market’s own order for the shopper’s group. When the test ends, the live version’s order applies again; promoting a winner publishes it like any other version, so it reaches the storefront through steps 1 to 5. Because the section renders the live order’s products for each page, which page a product is on follows the live order; its position within the page follows the variant.

What the theme must provide

  • Depict’s grid markup. The <depict-grid-start-beacon> and <depict-grid-end-beacon> elements around the product loop, and data-depict-product-id="{{ product.id }}" on every product card or an element inside it. See Content features theme integration. Depict’s theme patcher adds these; keep them when you rework the section, or the script cannot find the grid or match the cards to the variant’s order.
  • Steps 1 to 5 above, on the theme that serves the test. Depict switches collection version tests on per store once they are in place.
  • The Depict Brand Features app embed switched on for the theme. See Theme setup. The storefront access key the script uses is written by the app; nothing to add.
The group is stored per browser in localStorage under depict_collection_ab_variant_{test id}, so a returning shopper keeps their group on the same device. Filtered and custom-sorted views keep the theme’s native order for both groups, exactly as the market order does in step 1; the group is still assigned. Inside the Depict portal’s live preview no test is served.

Verify on the storefront

  1. Start a test in the Depict app on the collection.
  2. Open the collection in two different browsers, in a market the test covers. Private windows of one browser share storage, so they land in the same group. In each browser’s console, read the group:
    Repeat in a fresh browser profile until one reads A and the other B.
  3. In each browser, check that the #depict-grid-rendering-script element has a non-empty data-collection-ab-test attribute and a non-empty data-storefront-access-key attribute. An empty test attribute means the embed found no running test for this market, or one of the variant metaobjects has no products. Without the access key the script serves no test and never writes the group key.
  4. Compare the order of the page’s products between the browsers with the two versions in the Depict app. The order follows each browser’s group.