SeriesPart 10 of Commerce architecture in practiceView the cluster →
Medusa & ArchitectureArticle

How we fixed Medusa catalogue filters that showed combinations customers could not buy

The client needed catalogue filters whose visible results respected colour, size, price and the card customers would actually click. We built a two-stage filtering and URL model around Medusa’s product search.

We Are Souk article cover: How we fixed Medusa catalogue filters that showed combinations customers could not buy
Souk EngineeringCommerce architectureAug 2026·6 min read
Key takeaways
  • Write the customer’s filter rule in plain language before designing the query.
  • Colour, size and price must match the same purchasable variant.
  • Product counts, filter counts and displayed cards must count the same kind of thing.
  • Price filters must use the customer’s active market and displayed price.
  • Test with deliberately misleading products, not only tidy catalogue examples.

The client needed purchasable results, not technically matching products

Medusa product data provides products, variants, options and prices. The project’s storefront, however, rendered colour-oriented cards and allowed customers to combine facets. A parent product could match red, size 40 and a price range across three different variants while offering no purchasable variant with the complete combination.

We built shareable URL facets, backend narrowing and a storefront compatibility pass tied to the displayed colour and selected size, followed by infinite pagination. That removed false matches from the visible journey and established the exact variant identity as the target for the next evolution of server-side filtering.

The frustrating result customers recognise

A customer selects red, size 40 and a price below €100. The catalogue shows a shoe. They open it and discover that the red option is size 39, the size 40 option is blue and only a different variant costs below €100. The product matched every filter somewhere, but no purchasable version matched the complete request.

This bug appears because a product and its variants are different levels of data. The product carries shared information such as title and category. Each variant represents a concrete combination of options and price. A filter is trustworthy only when all variant-level choices—colour, size, price and sometimes availability—are satisfied by one variant the customer can buy.

Write the rule before writing the database query

Our rule was: show a catalogue card when the product matches product-level choices such as category, and one variant belonging to that card’s colour matches every selected option and the active-market price. This sentence identifies the unit at every stage. It is more valuable than beginning with joins and checkboxes.

The project displayed one card per colour, so products, variants and cards were three different counting units. A product with four colours could become four cards. If the page says ‘20 results’, the team must decide whether that means products, variants or visible colour cards. Counts are part of the customer promise, not a decorative number.

How the false match happens

A tempting database query asks separately: does this product have a red variant, does it have a size-40 variant and does it have a variant below €100? Different variants can answer yes to each question. The parent product passes even though no child variant satisfies all conditions together.

The correction is to ask whether one variant identity satisfies red, size 40 and the market price range at the same time. Product-level category and tags can remain outside that check. Ideally the server returns the matching variant identifier so the card displays a price, image, stock state and link derived from something that genuinely satisfies the request.

Move precision towards the server

The storefront expanded products into colour cards and removed false matches by checking that a selected size existed in the same displayed colour. This immediately improved the relevance of the visible catalogue without forcing the customer to understand the underlying product model.

The strongest long-term contract is for the server to return the matching variant identities directly. Measuring the difference between server results and cards that survive client filtering shows where that evolution creates the most value and prevents a useful storefront safeguard from becoming an invisible permanent dependency.

Filter counts need a product decision

After selecting size 40, should the colour counts show only colours available in that size? Usually yes. After selecting red, should the colour list still show blue as an addable choice? Many shops use ‘disjunctive’ behaviour: each filter count respects all other dimensions but temporarily ignores its own selection. This lets customers expand a colour choice without losing alternatives.

There is no correct count until the question is defined. Counts must also respect publication, sales channel, market price and any availability rule used by the grid. If a colour says 12 while only three cards can render, the system is answering different questions in different places. Customers experience that as broken navigation.

Price filtering belongs to the active market

A range of 50 to 100 has no meaning without currency, regional price list and tax treatment. Filter the same calculated price the card displays. Do not compare a euro base price and show a converted pound price. Decide whether thresholds include VAT and make one-cent boundaries consistent.

A shared filter URL reproduces the chosen rules, not a historical snapshot. Prices may change after the link is copied. If a campaign requires a frozen product set, use a curated collection or campaign snapshot. Ordinary catalogue filtering should remain live and truthful to the market in which the visitor opens it.

Pagination must count what the grid can show

If the server paginates products and the browser later expands or removes colour cards, a page can contain few visible results even though matching products exist later. Infinite scrolling may stop too early or make extra requests. Loading sooner improves perceived speed but does not fix the mismatch.

The strongest design paginates the entity the grid actually displays. If that is not practical, the API must return enough information for the storefront to continue fetching until it fills a visible page, with a sensible request limit. Sorting needs stable tie-breakers, and requests from old filter selections should be cancelled or ignored when they arrive late.

Use catalogue examples designed to fool the filter

Create a product where red has the requested size but the wrong price, blue has the right price but wrong size and green is unavailable. It must not match a red-plus-size-plus-price request unless one red variant satisfies all conditions. Add missing options, duplicate labels, regional prices and unpublished variants.

Then compare API results, filter counts and rendered cards for the same URL. Test refresh, sharing, back and forward navigation, sorting, clear-all and mobile controls. Track zero-result rate, query time, pages fetched per visible grid and the difference between server results and rendered cards. Trustworthy filtering is proven when every visible result can fulfil the customer’s complete request.

The trustworthy-filter checklist

Separate product choices from variant choices. Require one variant to satisfy the full variant request. Resolve price in the active market. Return the matching variant. Define what every count represents. Paginate the same visible unit. Keep selected filters in a shareable, canonical URL.

Finally, decide which filtered URLs deserve search indexation separately from whether the links work for customers. A good catalogue does not merely offer many controls. It gives predictable answers, preserves navigation and never advertises a combination the customer cannot purchase.

FAQ

Common questions

How can a product match when no variant does?

Separate checks can find colour on one variant, size on another and price on a third. The query must require one variant identity to satisfy all variant-level conditions together.

Should filter counts change after a selection?

Usually yes, according to a documented rule. Counts should respect other active dimensions and count the same unit the grid communicates.

Should every filtered URL be indexed by search engines?

No. Keep links functional and shareable, but index only deliberate combinations with search value. Unlimited thin combinations can create a large duplicate crawl surface.

Read next
Keep the useful ideas coming

One practical commerce field note at a time.

Join the WeAreSouk journal for grounded stories about Medusa, Shopify, AI, integrations and the systems behind serious commerce.

Working on a similar problem?Bring us the business constraint. We’ll help map the system behind it.Talk to Souk →
Souk AI · online now

Turn the article into an implementation plan.

Ask how this applies to your store, your stack, or your current bottleneck.

01 Describe your current setup.02 Name the workflow or signal that feels unreliable.03 Get a practical first architecture back.
I can help map this article to your stack. Tell me what you sell, what platform you use, and where the medusa & architecture question hurts.
Continue the cluster

Commerce architecture in practice

Start a conversation

Tell us what commerce needs to do for your business.

No scheduling maze. Send the context, the constraint or the idea. We will read it and come back to you directly.